Repository: bitfinexcom/bitfinex-api-py (language: python)

Issue:
Little fixes
This pull request is pretty small and just fixes a few issues I picked up on when manuell testing the python honey framework client.


Discussion / hints:

Fix patch:
diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE
deleted file mode 100644
index dc4cbeb2..00000000
--- a/.github/ISSUE_TEMPLATE
+++ /dev/null
@@ -1,13 +0,0 @@
-#### Issue type
-- [ ] bug
-- [ ] missing functionality
-- [ ] performance
-- [ ] feature request
-
-#### Brief description
-
-#### Steps to reproduce
--
-
-##### Additional Notes:
--
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000..d1f5f1df
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,35 @@
+## I'm submitting a...
+
+- [ ] bug report;
+- [ ] feature request;
+- [ ] documentation change;
+
+## What is the expected behaviour?
+<!--- Describe the expected behaviour in details -->
+
+## What is the current behaviour?
+<!--- Describe the current behaviour in details -->
+
+## Possible solution (optional)
+<!-- If you have a solution proposal, please explain it here -->
+<!-- If your solution includes implementation, you should also open a PR with this as related issue -->
+<!-- You can delete this section if you don't want to suggest a possible solution -->
+
+A possible solution could be...
+
+## Steps to reproduce (for bugs)
+<!-- You can delete this section if you are not submitting a bug report -->
+
+1. &nbsp;
+2. &nbsp;
+3. &nbsp;
+
+### Python version
+<!-- Indicate your python version here -->
+<!-- You can print it using `python3 --version`-->
+Python 3.10.6 x64
+
+### Mypy version
+<!-- Indicate your mypy version here -->
+<!-- You can print it using `python3 -m mypy --version`-->
+mypy 0.991 (compiled: yes)
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE
deleted file mode 100644
index b4cce303..00000000
--- a/.github/PULL_REQUEST_TEMPLATE
+++ /dev/null
@@ -1,15 +0,0 @@
-### Description:
-...
-
-### Breaking changes:
-- [ ]
-
-### New features:
-- [ ]
-
-### Fixes:
-- [ ]
-
-### PR status:
-- [ ] Version bumped
-- [ ] Change-log updated
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..f08ca31c
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,32 @@
+# Description
+<!--- Describe your changes in detail -->
+
+## Motivation and Context
+<!--- Why is this change required? What problem does it solve? -->
+
+## Related Issue
+<!--- If suggesting a new feature or change, please discuss it in an issue first -->
+<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
+<!--- Please link to the issue here: -->
+PR fixes the following issue: 
+
+## Type of change
+<!-- Select the most suitable choice and remove the others from the checklist -->
+
+- [ ] Bug fix (non-breaking change which fixes an issue);
+- [ ] New feature (non-breaking change which adds functionality);
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected);
+- [ ] This change requires a documentation update;
+
+# Checklist:
+
+- [ ] My code follows the style guidelines of this project;
+- [ ] I have performed a self-review of my code;
+- [ ] I have commented my code, particularly in hard-to-understand areas;
+- [ ] I have made corresponding changes to the documentation;
+- [ ] My changes generate no new warnings;
+- [ ] I have added tests that prove my fix is effective or that my feature works;
+- [ ] New and existing unit tests pass locally with my changes;
+- [ ] Mypy returns no errors or warnings when run on the root package;
+- [ ] Pylint returns a score of 10.00/10.00 when run on the root package;
+- [ ] I have updated the library version and updated the CHANGELOG;
\ No newline at end of file
diff --git a/.github/workflows/bitfinex-api-py-ci.yml b/.github/workflows/bitfinex-api-py-ci.yml
new file mode 100644
index 00000000..295ce9f0
--- /dev/null
+++ b/.github/workflows/bitfinex-api-py-ci.yml
@@ -0,0 +1,31 @@
+name: bitfinex-api-py-ci
+
+on:
+  push:
+    branches:
+      - v3-beta
+  pull_request:
+    branches:
+      - v3-beta
+
+permissions:
+  contents: read
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    steps: 
+      - uses: actions/checkout@v3
+      - name: Set up Python 3.8
+        uses: actions/setup-python@v4
+        with:
+          python-version: '3.8'
+      - name: Install bitfinex-api-py's dependencies
+        run: python -m pip install -r dev-requirements.txt
+      - name: Lint the project with pylint (and fail if score is lower than 10.00/10.00)
+        run: python -m pylint bfxapi
+      - name: Run mypy to check the correctness of type hinting (and fail if any error or warning is found)
+        run: python -m mypy bfxapi
+      - name: Execute project's unit tests (unittest)
+        run: python -m unittest bfxapi.tests
diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml
deleted file mode 100644
index 70bba55c..00000000
--- a/.github/workflows/python-app.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-# This workflow will install Python dependencies, run tests and lint with a single version of Python
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
-
-name: Python application
-
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    branches: [ master ]
-
-jobs:
-  build:
-
-    runs-on: ubuntu-latest
-
-    steps:
-    - uses: actions/checkout@v2
-    - name: Set up Python 3.9
-      uses: actions/setup-python@v2
-      with:
-        python-version: 3.9
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        pip install flake8 pytest
-        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
-    - name: Lint with flake8
-      run: |
-        # stop the build if there are Python syntax errors or undefined names
-        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
-        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
-        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
-    - name: Test with pytest
-      run: |
-        pytest
diff --git a/.gitignore b/.gitignore
index 77baeafb..6137a9d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,11 @@
-__pycache__
-*.pyc
-.vscode
-*.log
-dist
-bitfinex_api_py.egg-info
+.vscode
+*.pyc
+*.log
+
+bitfinex_api_py.egg-info
+
+__pycache__
+
+dist
+venv
+!.gitkeep
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 00000000..996e616b
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,33 @@
+[MAIN]
+py-version=3.8.0
+
+[MESSAGES CONTROL]
+disable=
+    multiple-imports,
+    missing-docstring,
+    logging-not-lazy,
+    logging-fstring-interpolation,
+    too-few-public-methods,
+    too-many-public-methods,
+    too-many-instance-attributes,
+    dangerous-default-value,
+    inconsistent-return-statements,
+
+[SIMILARITIES]
+min-similarity-lines=6
+
+[VARIABLES]
+allowed-redefined-builtins=type,dir,id,all,format,len
+
+[FORMAT]
+max-line-length=120
+expected-line-ending-format=LF
+
+[BASIC]
+good-names=id,on,pl,t,ip,tf,A,B,C,D,E,F
+
+[TYPECHECK]
+generated-members=websockets
+
+[STRING]
+check-quote-consistency=yes
diff --git a/.travis.yml b/.travis.yml
index 1d1a3b52..c9210f67 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,11 @@
 language: python
 python:
-  - "3.8.2"
+  - "3.8.0"
+before_install:
+  - python -m pip install --upgrade pip
 install:
-  - python3.8 -m pip install -r requirements.txt
+  - pip install -r dev-requirements.txt
 script:
-  - pylint --rcfile=pylint.rc bfxapi
-  - pytest
+  - python -m pylint bfxapi
+  - python -m mypy bfxapi
+  - python -m unittest bfxapi.tests
diff --git a/CHANGELOG b/CHANGELOG
deleted file mode 100644
index 8d6d1380..00000000
--- a/CHANGELOG
+++ /dev/null
@@ -1,172 +0,0 @@
-2.0.6
--) Added id field to _parse_trade
-
-2.0.5
--) Show correct fees for withdraws
-
-2.0.4
--) Added missing ID to ledgers.py
-
-2.0.3
--) Implemented Liquidations endpoint (REST)
-
-2.0.2
--) Use private host for auth-based requests
-
-2.0.1
--) Added User Settings Write/Read/Delete endpoints (REST)
--) Added Balance Available for Orders/Offers endpoint (REST)
--) Added Alerts endpoints (REST)
--) Fixed trades handling error
-
-2.0.0
--) Implemented Movement endpoints (REST)
--) Fixed unawaited stop
--) Changed account's trade execution (te) and trade update (tu) handling
-
-1.3.4
--) Fixed undefined p_sub issue in subscription_manager.py
--) Added submit cancel all funding orders endpoint (REST)
--) Added get all exchange pairs endpoint (REST)
-
-1.3.3
--) Fixed socket.send() issue (IndexError: deque index out of range)
-
-1.3.2
--) Implemented Merchants endpoints (REST)
-
-1.3.1
--) Handle exception of asyncio.get_event_loop() | Related to v1.2.8
-
-1.3.0
--) Adjusted get_trades() to allow symbol to be None and get trades for all symbols
-
-1.2.8
--) Bugfix - It is possible to call bfx.ws.run() from an already running event loop
-
-1.2.7
--) Added ws support for Python 3.9 and 3.10
-
-1.2.6
--) Updated websockets to 9.1
-
-1.2.5
--) Adjusted get_order_history() rest endpoint
-
-1.2.4
--) Added example of MARKET order with price=None
-
-1.2.3
--) Tests adjusted
-
-1.2.2
--) WS bugfix (exception InvalidStatusCode not handled)
-
-1.2.1
--) Added orderbook implementation example (ws)
-
-1.2.0
--) Implemented Margin Info (rest)
--) Implemented claim position (rest)
--) When max_retries == 0 continue forever to retry (websocket)
-
-1.1.15
--) Added 'ids' parameter to get_order_history()
--) Added an example to show how it is possible to spawn multiple bfx ws instances to comply with the open subscriptions number constraint (max. 25)
--) Implemented Funding Trades (rest)
-
-1.1.14
--) bfx_websockets.py ERRORS dictionary now contains a message for error number 10305
-
-1.1.13
--) Adding balance_available to the Wallet.
-
-1.1.12
--) Applied clientside fix to get_public_trades() (in case of multiple trades at the same timestamp they should be ordered by id)
--) Invalid orders are now removed from pending_orders
--) FOK orders cancelled are now removed from pending_orders
-
-1.1.11
--) Removed pendingOrders from BfxWebsocket() (it was not used anywhere)
--) Fixed issue in confirm_order_new() (the keys of the dict pending_orders are the cids of the orders, and not the ids)
-
-1.1.10
-- Fixed get_seed_candles() [backwards compatible]
-
-1.1.9
-- Implemented PULSE endpoints (rest)
-- Updated pyee and changed deprecated class EventEmitter() -> AsyncIOEventEmitter() to make it work with all Python 3.X versions
-- Implemented Foreign exchange rate endpoint (rest)
-- Implemented Market average price endpoint (rest)
-- Implemented Generate invoice endpoint (rest)
-- Implemented Keep funding endpoint (rest)
-- Implemented Cancel order multi endpoint (rest)
-- Implemented Public Stats endpoint (rest)
-- Implemented Order Multi OP endpoint (rest)
-- Implemented Public Tickers History (rest)
-- Implemented Public Funding Stats (rest)
-- Updated dependencies in setup.py
-
-1.1.8
-- Adds support for websocket events pu, pn and pu
-
-1.1.7
-- Adds rest.get_ledgers
-
-1.1.6
-- Adds 'new_ticker' websocket event stream
-- Adds 'ws.stop' function to kill all websocket connections
-- Fixes Position modal loading from raw array
-
-1.1.5
-- Fixes 100% CPU utilization bug with the generic_websocket event emitter thread
-
-1.1.4
-- Locks mutex when sending websocket messages
-- Fix py3.8 stricter linting errors
-
-1.1.3
-- Adds ability to specify channel_filter in client
-
-1.1.2
-- Adds aff_code to WS and Rest submit order functions
-
-1.1.1
-- Rework README with new bfx templates
-- Generate /docs/rest_v2.md
-- Generate /docs/ws_v2.md
-- Update comments for doc generation
-- Allow only python3 installs in setup.py
-
-1.1.0
-
-- Adds rest.submit_funding_offer
-- Adds rest.submit_cancel_funding_offer
-- Adds rest.submit_wallet_transfer
-- Adds rest.get_wallet_deposit_address
-- Adds rest.create_wallet_deposit_address
-- Adds rest.submit_wallet_withdraw
-- Adds rest.submit_order
-- Adds rest.submit_cancel_order
-- Adds rest.submit_update_order
-- Updates websocket notification event to use Notfication model object
-
-1.0.1
-
-- Added ws event `status_update`
-- Added rest function `get_derivative_status`
-- Added rest function `get_derivative_statuses`
-- Added rest function `set_derivative_collateral`
-- Added channel support `status`
-- Added create_event_emitter as override in generic_websocket 
-  for custom event emitters
-
-1.0.0
-
-- Removal of camel-casing file naming and git duplicates
-  i.e bfx.client instead of bfx.Client
-
-
-0.0.1
-
-- Added change-log and PR/Issue templates
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..99ebd534
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+  and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+  overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+  advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+  address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+support@bitfinex.com (Bitfinex).
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior,  harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/LICENSE b/LICENSE
index 261eeb9e..4947287f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,3 +1,4 @@
+
                                  Apache License
                            Version 2.0, January 2004
                         http://www.apache.org/licenses/
@@ -173,29 +174,4 @@
       incurred by, or claims asserted against, such Contributor by reason
       of your accepting any such warranty or additional liability.
 
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
+   END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/README.md b/README.md
index eb8d3928..0b574eed 100644
--- a/README.md
+++ b/README.md
@@ -1,175 +1,271 @@
-# Bitfinex Trading Library for Python - Bitcoin, Ethereum, Ripple and more
+# bitfinex-api-py (v3-beta)
 
-![https://api.travis-ci.org/bitfinexcom/bitfinex-api-py.svg?branch=master](https://api.travis-ci.org/bitfinexcom/bitfinex-api-py.svg?branch=master)
+Official implementation of the [Bitfinex APIs (V2)](https://docs.bitfinex.com/docs) for `Python 3.8+`.
 
-A Python reference implementation of the Bitfinex API for both REST and websocket interaction.
+> **DISCLAIMER:** \
+Production use of v3.0.0b1 (and all future beta versions) is HIGHLY discouraged. \
+Beta versions should not be used in applications which require user authentication. \
+Provide your API-KEY/API-SECRET, and manage your account and funds at your own risk.
 
-# Features
-- Official implementation
-- Websocket V2 and Rest V2
-- Connection multiplexing
-- Order and wallet management
-- All market data feeds
+### Features
+- User-friendly implementations for 75+ public and authenticated REST endpoints.
+    * A complete list of available REST endpoints can be found [here](https://docs.bitfinex.com/reference).
+- New WebSocket client to ensure fast, secure and persistent connections.
+    * Support for all public channels + authenticated events and inputs (a list can be found [here](https://docs.bitfinex.com/docs/ws-public)).
+    * Automatic reconnection system in case of network failure (both client and server side).
+        - The WebSocket client logs every reconnection failure, success and attempt (as well as other events).
+    * Connection multiplexing to allow subscribing to a large number of public channels (without affecting performances).
+        - The WebSocket server sets a limit of 25 subscriptions per connection, connection multiplexing allows the WebSocket client to bypass this limit.
+- Full type-hinting and type-checking support with [`mypy`](https://github.com/python/mypy). 
+    * This allow text editors to show helpful hints about the value of a variable: ![example](https://i.imgur.com/aDjapcN.png "Type-hinting example on a random code snippet")
+
+---
 
 ## Installation
 
-Clone package into PYTHONPATH:
-```sh
-git clone https://github.com/bitfinexcom/bitfinex-api-py.git
-cd bitfinex-api-py
+To install the latest beta release of `bitfinex-api-py`:
+```bash
+python3 -m pip install --pre bitfinex-api-py
 ```
-
-Or via pip:
-```sh
-python3 -m pip install bitfinex-api-py
+To install a specific beta version:
+```bash
+python3 -m pip install bitfinex-api-py==3.0.0b1
 ```
 
-Run the trades/candles example:
-```sh
-cd bfxapi/examples/ws
-python3 subscribe_trades_candles.py
-```
+## Basic usage
+
+---
+
+### Index
+
+* [WebSocket Client documentation](#websocket-client-documentation)
+* [Building the source code](#building-the-source-code)
+* [How to contribute](#how-to-contribute)
+
+---
 
-## Quickstart
+# WebSocket Client documentation
+
+1. [Instantiating the client](#instantiating-the-client)
+    * [Authentication](#authentication)
+    * [Configuring the logger](#configuring-the-logger)
+2. [Running the client](#running-the-client)
+    * [Closing the connection](#closing-the-connection)
+3. [Subscribing to public channels](#subscribing-to-public-channels)
+    * [Setting a custom `sub_id`](#setting-a-custom-sub_id)
+4. [Listening to events](#listening-to-events)
+5. [Advanced Features](#advanced-features)
+    * [Connection multiplexing](#connection-multiplexing)
+    * [Sending custom notifications](#sending-custom-notifications)
+    * [Handling reconnections in case of network failure](#handling-reconnections-in-case-of-network-failure)
+
+## Instantiating the client
 
 ```python
-import os
-import sys
-from bfxapi import Client, Order
+bfx = Client(wss_host=PUB_WSS_HOST)
+```
+
+`Client::wss` contains an instance of `BfxWebSocketClient` (core implementation of the WebSocket client). \
+The `wss_host` argument is used to indicate the URL to which the WebSocket client should connect. \
+The `bfxapi` package exports 2 constants to quickly set this URL:
+
+Constant | URL | When to use
+--- | --- | ---
+WSS_HOST | wss://api.bitfinex.com/ws/2 | Suitable for all situations, supports authentication.
+PUB_WSS_HOST | wss://api-pub.bitfinex.com/ws/2 | For public uses only, doesn't support authentication.
+
+PUB_WSS_HOST is recommended over WSS_HOST for applications that don't require authentication.
+
+> **NOTE:** The `wss_host` parameter is optional, and the default value is WSS_HOST.
 
+### Authentication
+
+### Configuring the logger
+
+`log_filename` (`Optional[str]`, default: `None`): \
+Relative path of the file where to save the logs the client will emit. \
+If not given, the client will emit logs on `stdout` (`stderr` for errors and warnings).
+
+`log_level` (`str`, default: `"INFO"`): \
+Available log levels are (in order): `ERROR`, `WARNING`, `INFO` and `DEBUG`. \
+The client will only log messages whose level is lower than or equal to `log_level`. \
+For example, if `log_level=WARNING`, the client will only log errors and warnings.
+
+```python
 bfx = Client(
-  API_KEY='<YOUR_API_KEY>',
-  API_SECRET='<YOUR_API_SECRET>'
+    [...],
+    log_filename="2023-03-26.log",
+    log_level="WARNING"
 )
+```
 
-@bfx.ws.on('authenticated')
-async def submit_order(auth_message):
-  await bfx.ws.submit_order('tBTCUSD', 19000, 0.01, Order.Type.EXCHANGE_MARKET)
+## Running the client
 
-bfx.ws.run()
+The client can be run using `BfxWebSocketClient::run`:
+```python
+bfx.wss.run()
 ```
 
-## Docs
+If an event loop is already running, users can start the client with `BfxWebSocketClient::start`:
+```python
+await bfx.wss.start()
+```
 
-* <b>[V2 Rest](docs/rest_v2.md)</b> - Documentation
-* <b>[V2 Websocket](docs/ws_v2.md)</b> - Documentation
+If the client succeeds in connecting to the server, it will emit the `open` event. \
+This is the right place for all bootstrap activities, such as subscribing to public channels. \
+To learn more about events and public channels, see [Listening to events](#listening-to-events) and [Subscribing to public channels](#subscribing-to-public-channels).
 
-## Examples
+```python
+@bfx.wss.on("open")
+async def on_open():
+    await bfx.wss.subscribe(Channel.TICKER, symbol="tBTCUSD")
+```
 
-#### Authenticate
+### Closing the connection
 
+Users can close the connection with the WebSocket server using `BfxWebSocketClient::close`:
 ```python
-bfx = Client(
-  API_KEY='<YOUR_API_KEY>',
-  API_SECRET='<YOUR_API_SECRET>'
-)
+await bfx.wss.close()
+```
 
-@bfx.ws.on('authenticated')
-async def do_something():
-  print ("Success!")
+A custom [close code number](https://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number), along with a verbose reason, can be given as parameters:
+```python
+await bfx.wss.close(code=1001, reason="Going Away")
+```
 
-bfx.ws.run()
+After closing the connection, the client will emit the `disconnection` event:
+```python
+@bfx.wss.on("disconnection")
+def on_disconnection(code: int, reason: str):
+    print(f"Closing connection with code: <{code}>. Reason: {reason}.")
 ```
 
-#### Subscribe to trades
+## Subscribing to public channels
 
+Users can subscribe to public channels using `BfxWebSocketClient::subscribe`:
 ```python
-from bfxapi import Client
+await bfx.wss.subscribe("ticker", symbol="tBTCUSD")
+```
 
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET
-)
+On each successful subscription, the client will emit the `subscribed` event:
+```python
+@bfx.wss.on("subscribed")
+def on_subscribed(subscription: subscriptions.Subscription):
+    if subscription["channel"] == "ticker":
+        print(f"{subscription['symbol']}: {subscription['subId']}") # tBTCUSD: f2757df2-7e11-4244-9bb7-a53b7343bef8
+```
 
-@bfx.ws.on('new_trade')
-def log_trade(trade):
-  print ("New trade: {}".format(trade))
+### Setting a custom `sub_id`
 
-@bfx.ws.on('connected')
-def start():
-  bfx.ws.subscribe('trades', 'tBTCUSD')
+The client generates a random `sub_id` for each subscription. \
+These values must be unique, as the client uses them to identify subscriptions. \
+However, it is possible to force this value by passing a custom `sub_id` to `BfxWebSocketClient::subscribe`:
 
-bfx.ws.run()
+```python
+await bfx.wss.subscribe("candles", key="trade:1m:tBTCUSD", sub_id="507f1f77bcf86cd799439011")
 ```
 
-#### Withdraw from wallet via REST
+## Listening to events
 
+Whenever the WebSocket client receives data, it will emit a specific event. \
+Users can either ignore those events or listen for them by registering callback functions. \
+These callback functions can also be asynchronous; in fact the client fully supports coroutines ([`asyncio`](https://docs.python.org/3/library/asyncio.html)).
+
+To add a listener for a specific event, users can use the decorator `BfxWebSocketClient::on`:
 ```python
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='DEBUG'
-)
-response = await bfx.rest.submit_wallet_withdraw("exchange", "tetheruse", 5, "0xc5bbb852f82c24327693937d4012f496cff7eddf")
-print ("Address: ", response.notify_info)
+@bfx.wss.on("candles_update")
+def on_candles_update(sub: subscriptions.Candles, candle: Candle):
+    print(f"Candle update for key <{sub['key']}>: {candle}")
 ```
-See the <b>[examples](https://github.com/bitfinexcom/bitfinex-api-py/tree/master/examples)</b> directory for more, like:
 
-- [Creating/updating an order](https://github.com/bitfinexcom/bitfinex-api-py/blob/master/bfxapi/examples/ws/send_order.py)
-- [Subscribing to orderbook updates](https://github.com/bitfinexcom/bitfinex-api-py/blob/master/bfxapi/examples/ws/resubscribe_orderbook.py)
-- [Withdrawing crypto](https://github.com/bitfinexcom/bitfinex-api-py/blob/master/bfxapi/examples/rest/transfer_wallet.py)
-- [Submitting a funding offer](https://github.com/bitfinexcom/bitfinex-api-py/blob/master/bfxapi/examples/rest/create_funding.py)
+The same can be done without using decorators:
+```python
+bfx.wss.on("candles_update", callback=on_candles_update)
+```
 
-For more info on how to use this library please see the example scripts in the `bfxapi/examples` directory. Here you will find usage of all interface exposed functions for both the rest and websocket.
+You can pass any number of events to register for the same callback function:
+```python
+bfx.wss.on("t_ticker_update", "f_ticker_update", callback=on_ticker_update)
+```
 
-Also please see [this medium article](https://medium.com/@Bitfinex/15f201ad20d4) for a tutorial.
+## Advanced features
 
-## FAQ
+### Connection multiplexing
 
-### Is there any rate limiting?
+`BfxWebSocketClient::run` and `BfxWebSocketClient::start` accept a `connections` argument:
+```python
+bfx.wss.run(connections=3)
+```
 
-For a Websocket connection there is no limit to the number of requests sent down the connection (unlimited order operations) however an account can only create 15 new connections every 5 mins and each connection is only able to subscribe to 30 inbound data channels. Fortunately this library handles all of the load balancing/multiplexing for channels and will automatically create/destroy new connections when needed, however the user may still encounter the max connections rate limiting error.
+`connections` indicates the number of connections to run concurrently (through connection multiplexing).
 
-For rest the base limit per-user is 1,000 orders per 5 minute interval, and is shared between all account API connections. It increases proportionally to your trade volume based on the following formula:
+Each of these connections can handle up to 25 subscriptions to public channels. \
+So, using `N` connections will allow the client to handle at most `N * 25` subscriptions. \
+You should always use the minimum number of connections necessary to handle all the subscriptions that will be made.
 
-1000 + (TOTAL_PAIRS_PLATFORM * 60 * 5) / (250000000 / USER_VOL_LAST_30d)
+For example, if you know that your application will subscribe to 75 public channels, 75 / 25 = 3 connections will be enough to handle all the subscriptions.
 
-Where TOTAL_PAIRS_PLATFORM is the number of pairs on the Bitfinex platform (currently ~101) and USER_VOL_LAST_30d is in USD.
+The default number of connections is 5; therefore, if the `connections` argument is not given, the client will be able to handle a maximum of 25 * 5 = 125 subscriptions.
 
-### Will I always receive an `on` packet?
+Keep in mind that using a large number of connections could slow down the client performance.
 
-No; if your order fills immediately, the first packet referencing the order will be an `oc` signaling the order has closed. If the order fills partially immediately after creation, an `on` packet will arrive with a status of `PARTIALLY FILLED...`
+The use of more than 20 connections is not recommended.
 
-For example, if you submit a `LIMIT` buy for 0.2 BTC and it is added to the order book, an `on` packet will arrive via ws2. After a partial fill of 0.1 BTC, an `ou` packet will arrive, followed by a final `oc` after the remaining 0.1 BTC fills.
+### Sending custom notifications
 
-On the other hand, if the order fills immediately for 0.2 BTC, you will only receive an `oc` packet.
+**Sending custom notifications requires user authentication.**
 
-### My websocket won't connect!
+Users can send custom notifications using `BfxWebSocketClient::notify`:
+```python
+await bfx.wss.notify({ "foo": 1 })
+```
 
-Did you call `client.Connect()`? :)
+Any data can be sent along with a custom notification.
 
-### nonce too small
+Custom notifications are broadcast by the server on all user's open connections. \
+So, each custom notification will be sent to every online client of the current user. \
+Whenever a client receives a custom notification, it will emit the `notification` event:
+```python
+@bfx.wss.on("notification")
+def on_notification(notification: Notification[Any]):
+    print(notification.data) # { "foo": 1 }
+```
+
+### Handling reconnections in case of network failure
 
-I make multiple parallel request and I receive an error that the nonce is too small. What does it mean?
+In case of network failure, the client will keep waiting until it is able to restore the connection with the server.
 
-Nonces are used to guard against replay attacks. When multiple HTTP requests arrive at the API with the wrong nonce, e.g. because of an async timing issue, the API will reject the request.
+The client will try to reconnect with exponential backoff; the backoff delay starts at three seconds and increases up to one minute.
 
-If you need to go parallel, you have to use multiple API keys right now.
+After a successful reconnection attempt, the client will emit the `reconnection` event.
 
-### How do `te` and `tu` messages differ?
+This event accepts two arguments: \
+`attemps` (`int`) which is the number of reconnection attempts (including the successful one), \
+`timedelta` (`datetime.timedelta`) which contains the amount of time the client has been down.
 
-A `te` packet is sent first to the client immediately after a trade has been matched & executed, followed by a `tu` message once it has completed processing. During times of high load, the `tu` message may be noticably delayed, and as such only the `te` message should be used for a realtime feed.
+Users can use this event for a variety of things, such as sending a notification if the client has been down for too long:
+```python
+@bfx.wss.on("reconnection")
+async def on_reconnection(attempts: int, timedelta: datetime.timedelta):
+    if timedelta.total_seconds() >= 60 * 60: # 60s * 60s = 3600s = 1h
+        await bfx.wss.notify(f"The client has been down for {timedelta}.")
+```
 
-### What are the sequence numbers for?
+---
 
-If you enable sequencing on v2 of the WS API, each incoming packet will have a public sequence number at the end, along with an auth sequence number in the case of channel `0` packets. The public seq numbers increment on each packet, and the auth seq numbers increment on each authenticated action (new orders, etc). These values allow you to verify that no packets have been missed/dropped, since they always increase monotonically.
+# Building the source code
 
-### What is the difference between R* and P* order books?
+## Testing (with unittest)
 
-Order books with precision `R0` are considered 'raw' and contain entries for each order submitted to the book, whereas `P*` books contain entries for each price level (which aggregate orders).
+## Linting the project with pylint
 
+## Using mypy to ensure correct type-hinting
 
-## Contributing
+---
 
-1. Fork it ( https://github.com/[my-github-username]/bitfinex/fork )
-2. Create your feature branch (`git checkout -b my-new-feature`)
-3. Commit your changes (`git commit -am 'Add some feature'`)
-4. Push to the branch (`git push origin my-new-feature`)
-5. Create a new Pull Request
+# How to contribute
 
-### Publish to Pypi
+## License
+This project is released under the `Apache License 2.0`.
 
-```
-python setup.py sdist
-twine upload dist/*
-```
+The complete license can be found here: https://www.apache.org/licenses/LICENSE-2.0.
diff --git a/bfxapi/__init__.py b/bfxapi/__init__.py
index 5b6918e2..b5832488 100644
--- a/bfxapi/__init__.py
+++ b/bfxapi/__init__.py
@@ -1,14 +1,6 @@
-"""
-This module is used to interact with the bitfinex api
-"""
+from .client import Client
 
-from .version import __version__
-from .client import Client, PUB_REST_HOST, PUB_WS_HOST, REST_HOST, WS_HOST
-from .models import (Order, Trade, OrderBook, Subscription, Wallet,
-                     Position, FundingLoan, FundingOffer, FundingCredit,
-                     Movement)
-from .websockets.generic_websocket import GenericWebsocket, Socket
-from .websockets.bfx_websocket import BfxWebsocket
-from .utils.decimal import Decimal
+from .urls import REST_HOST, PUB_REST_HOST, \
+    WSS_HOST, PUB_WSS_HOST
 
-NAME = 'bfxapi'
+from .version import __version__
diff --git a/bfxapi/client.py b/bfxapi/client.py
index 1d2d57c9..db54b730 100644
--- a/bfxapi/client.py
+++ b/bfxapi/client.py
@@ -1,24 +1,35 @@
-"""
-This module exposes the core bitfinex clients which includes both
-a websocket client and a rest interface client
-"""
+from typing import List, Optional
 
-# pylint: disable-all
-
-from .websockets.bfx_websocket import BfxWebsocket
-from .rest.bfx_rest import BfxRest
-from .constants import *
+from .rest import BfxRestInterface
+from .websocket import BfxWebsocketClient
+from .urls import REST_HOST, WSS_HOST
 
 class Client:
-    """
-    The bfx client exposes rest and websocket objects
-    """
+    def __init__(
+            self,
+            api_key: Optional[str] = None,
+            api_secret: Optional[str] = None,
+            filters: Optional[List[str]] = None,
+            *,
+            rest_host: str = REST_HOST,
+            wss_host: str = WSS_HOST,
+            log_filename: Optional[str] = None,
+            log_level: str = "INFO"
+    ):
+        credentials = None
+
+        if api_key and api_secret:
+            credentials = { "api_key": api_key, "api_secret": api_secret, "filters": filters }
+
+        self.rest = BfxRestInterface(
+            host=rest_host,
+            credentials=credentials
+        )
 
-    def __init__(self, API_KEY=None, API_SECRET=None, rest_host=REST_HOST,
-                 ws_host=WS_HOST, create_event_emitter=None, logLevel='INFO', dead_man_switch=False,
-                 ws_capacity=25, channel_filter=[], *args, **kwargs):
-        self.ws = BfxWebsocket(API_KEY=API_KEY, API_SECRET=API_SECRET, host=ws_host,
-                               logLevel=logLevel, dead_man_switch=dead_man_switch, channel_filter=channel_filter,
-                               ws_capacity=ws_capacity, create_event_emitter=create_event_emitter, *args, **kwargs)
-        self.rest = BfxRest(API_KEY=API_KEY, API_SECRET=API_SECRET, host=rest_host,
-                            logLevel=logLevel, *args, **kwargs)
+        self.wss = BfxWebsocketClient(
+            host=wss_host,
+            credentials=credentials,
+            log_filename=log_filename,
+            log_level=log_level
+        )
+        
\ No newline at end of file
diff --git a/bfxapi/constants.py b/bfxapi/constants.py
deleted file mode 100644
index a0c65251..00000000
--- a/bfxapi/constants.py
+++ /dev/null
@@ -1,4 +0,0 @@
-REST_HOST = 'https://api.bitfinex.com/v2'
-WS_HOST = 'wss://api.bitfinex.com/ws/2'
-PUB_REST_HOST = 'https://api-pub.bitfinex.com/v2'
-PUB_WS_HOST = 'wss://api-pub.bitfinex.com/ws/2'
\ No newline at end of file
diff --git a/bfxapi/enums.py b/bfxapi/enums.py
new file mode 100644
index 00000000..9b06bc2b
--- /dev/null
+++ b/bfxapi/enums.py
@@ -0,0 +1,50 @@
+from enum import Enum
+
+class OrderType(str, Enum):
+    LIMIT = "LIMIT"
+    EXCHANGE_LIMIT = "EXCHANGE LIMIT"
+    MARKET = "MARKET"
+    EXCHANGE_MARKET = "EXCHANGE MARKET"
+    STOP = "STOP"
+    EXCHANGE_STOP = "EXCHANGE STOP"
+    STOP_LIMIT = "STOP LIMIT"
+    EXCHANGE_STOP_LIMIT = "EXCHANGE STOP LIMIT"
+    TRAILING_STOP = "TRAILING STOP"
+    EXCHANGE_TRAILING_STOP = "EXCHANGE TRAILING STOP"
+    FOK = "FOK"
+    EXCHANGE_FOK = "EXCHANGE FOK"
+    IOC = "IOC"
+    EXCHANGE_IOC = "EXCHANGE IOC"
+
+class FundingOfferType(str, Enum):
+    LIMIT = "LIMIT"
+    FRR_DELTA_FIX = "FRRDELTAFIX"
+    FRR_DELTA_VAR = "FRRDELTAVAR"
+
+class Flag(int, Enum):
+    HIDDEN = 64
+    CLOSE = 512
+    REDUCE_ONLY = 1024
+    POST_ONLY = 4096
+    OCO = 16384
+    NO_VAR_RATES = 524288
+
+class Error(int, Enum):
+    ERR_UNK = 10000
+    ERR_GENERIC = 10001
+    ERR_CONCURRENCY = 10008
+    ERR_PARAMS = 10020
+    ERR_CONF_FAIL = 10050
+    ERR_AUTH_FAIL = 10100
+    ERR_AUTH_PAYLOAD = 10111
+    ERR_AUTH_SIG = 10112
+    ERR_AUTH_HMAC = 10113
+    ERR_AUTH_NONCE = 10114
+    ERR_UNAUTH_FAIL = 10200
+    ERR_SUB_FAIL = 10300
+    ERR_SUB_MULTI = 10301
+    ERR_SUB_UNK = 10302
+    ERR_SUB_LIMIT = 10305
+    ERR_UNSUB_FAIL = 10400
+    ERR_UNSUB_NOT = 10401
+    ERR_READY = 11000
diff --git a/bfxapi/examples/rest/create_funding.py b/bfxapi/examples/rest/create_funding.py
deleted file mode 100644
index db168260..00000000
--- a/bfxapi/examples/rest/create_funding.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import os
-import sys
-import asyncio
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import WS_HOST, REST_HOST
-
-API_KEY=os.getenv("BFX_KEY")
-API_SECRET=os.getenv("BFX_SECRET")
-
-# Create funding requires private hosts
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='DEBUG',
-  ws_host=WS_HOST,
-  rest_host=REST_HOST
-)
-
-async def create_funding():
-  response = await bfx.rest.submit_funding_offer("fUSD", 1000, 0.012, 7)
-  # response is in the form of a Notification object
-  # notify_info is in the form of a FundingOffer
-  print ("Offer: ", response.notify_info)
-
-async def cancel_funding():
-  response = await bfx.rest.submit_cancel_funding_offer(41235958)
-  # response is in the form of a Notification object
-  # notify_info is in the form of a FundingOffer
-  print ("Offer: ", response.notify_info)
-
-async def run():
-  await create_funding()
-  await cancel_funding()
-
-t = asyncio.ensure_future(run())
-asyncio.get_event_loop().run_until_complete(t)
diff --git a/bfxapi/examples/rest/create_order.py b/bfxapi/examples/rest/create_order.py
deleted file mode 100644
index 94e96407..00000000
--- a/bfxapi/examples/rest/create_order.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import os
-import sys
-import asyncio
-import time
-sys.path.append('../../../')
-from bfxapi import Client
-from bfxapi.constants import WS_HOST, REST_HOST
-from bfxapi.models import OrderType
-
-API_KEY=os.getenv("BFX_KEY")
-API_SECRET=os.getenv("BFX_SECRET")
-
-# Create order requires private hosts
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='DEBUG',
-  ws_host=WS_HOST,
-  rest_host=REST_HOST
-)
-
-async def create_order():
-  response = await bfx.rest.submit_order(symbol="tBTCUSD", amount=10, price=None, market_type=OrderType.MARKET)
-  # response is in the form of a Notification object
-  for o in response.notify_info:
-    # each item is in the form of an Order object
-    print ("Order: ", o)
-
-async def cancel_order():
-  response = await bfx.rest.submit_cancel_order(1185510865)
-  # response is in the form of a Notification object
-  # notify_info is in the form of an order object
-  print ("Order: ", response.notify_info)
-
-async def update_order():
-  response = await bfx.rest.submit_update_order(1185510771, price=15, amount=0.055)
-  # response is in the form of a Notification object
-  # notify_info is in the form of an order object
-  print ("Order: ", response.notify_info)
-
-async def run():
-  await create_order()
-  await cancel_order()
-  await update_order()
-
-t = asyncio.ensure_future(run())
-asyncio.get_event_loop().run_until_complete(t)
diff --git a/bfxapi/examples/rest/get_authenticated_data.py b/bfxapi/examples/rest/get_authenticated_data.py
deleted file mode 100644
index a557de26..00000000
--- a/bfxapi/examples/rest/get_authenticated_data.py
+++ /dev/null
@@ -1,110 +0,0 @@
-import os
-import sys
-import asyncio
-import time
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import WS_HOST, REST_HOST
-
-API_KEY=os.getenv("BFX_KEY")
-API_SECRET=os.getenv("BFX_SECRET")
-
-# Retrieving authenticated data requires private hosts
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='DEBUG',
-  ws_host=WS_HOST,
-  rest_host=REST_HOST
-)
-
-now = int(round(time.time() * 1000))
-then = now - (1000 * 60 * 60 * 24 * 10) # 10 days ago
-
-async def log_wallets():
-  wallets = await bfx.rest.get_wallets()
-  print ("Wallets:")
-  [ print (w) for w in wallets ]
-
-async def log_active_orders():
-  orders = await bfx.rest.get_active_orders('tBTCUSD')
-  print ("Orders:")
-  [ print (o) for o in orders ]
-
-async def log_orders_history():
-  orders = await bfx.rest.get_order_history('tBTCUSD', 0, then)
-  print ("Orders:")
-  [ print (o) for o in orders ]
-
-async def log_active_positions():
-  positions = await bfx.rest.get_active_position()
-  print ("Positions:")
-  [ print (p) for p in positions ]
-
-async def log_trades():
-  trades = await bfx.rest.get_trades(symbol='tBTCUSD', start=0, end=then)
-  print ("Trades:")
-  [ print (t) for t in trades]
-
-async def log_order_trades():
-  order_id = 1151353463
-  trades = await bfx.rest.get_order_trades('tBTCUSD', order_id)
-  print ("Trade orders:")
-  [ print (t) for t in trades]
-
-async def log_funding_offers():
-  offers = await bfx.rest.get_funding_offers('fBTC')
-  print ("Offers:")
-  [ print (o) for o in offers]
-
-async def log_funding_offer_history():
-  offers = await bfx.rest.get_funding_offer_history('fBTC', 0, then)
-  print ("Offers history:")
-  [ print (o) for o in offers]
-
-async def log_funding_loans():
-  loans = await bfx.rest.get_funding_loans('fBTC')
-  print ("Funding loans:")
-  [ print (l) for l in loans ]
-
-async def log_funding_loans_history():
-  loans = await bfx.rest.get_funding_loan_history('fBTC', 0, then)
-  print ("Funding loan history:")
-  [ print (l) for l in loans ]
-
-async def log_funding_credits():
-  credits = await bfx.rest.get_funding_credits('fBTC')
-  print ("Funding credits:")
-  [ print (c) for c in credits ]
-
-async def log_funding_credits_history():
-  credit = await bfx.rest.get_funding_credit_history('fBTC', 0, then)
-  print ("Funding credit history:")
-  [ print (c) for c in credit ]
-
-async def log_margin_info():
-  margin_info = await bfx.rest.get_margin_info('tBTCUSD')
-  print(margin_info)
-  sym_all = await bfx.rest.get_margin_info('sym_all')  # list of Margin Info
-  for margin_info in sym_all:
-      print(margin_info)
-  base = await bfx.rest.get_margin_info('base')
-  print(base)
-
-async def run():
-  await log_wallets()
-  await log_active_orders()
-  await log_orders_history()
-  await log_active_positions()
-  await log_trades()
-  await log_order_trades()
-  await log_funding_offers()
-  await log_funding_offer_history()
-  await log_funding_credits()
-  await log_funding_credits_history()
-  await log_margin_info()
-  
-
-t = asyncio.ensure_future(run())
-asyncio.get_event_loop().run_until_complete(t)
diff --git a/bfxapi/examples/rest/get_liquidations.py b/bfxapi/examples/rest/get_liquidations.py
deleted file mode 100644
index f7dd896c..00000000
--- a/bfxapi/examples/rest/get_liquidations.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import os
-import sys
-import asyncio
-import time
-sys.path.append('../../../')
-
-from bfxapi import Client, PUB_REST_HOST
-
-bfx = Client(
-  logLevel='INFO',
-  rest_host=PUB_REST_HOST
-)
-
-now = int(round(time.time() * 1000))
-then = now - (1000 * 60 * 60 * 24 * 10) # 10 days ago
-
-async def get_liquidations():
-  liquidations = await bfx.rest.get_liquidations(start=then, end=now)
-  print(liquidations)
-
-asyncio.get_event_loop().run_until_complete(get_liquidations())
diff --git a/bfxapi/examples/rest/get_public_data.py b/bfxapi/examples/rest/get_public_data.py
deleted file mode 100644
index 6d1e246f..00000000
--- a/bfxapi/examples/rest/get_public_data.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import os
-import sys
-import asyncio
-import time
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-
-# Retrieving public data requires public hosts
-bfx = Client(
-  logLevel='DEBUG',
-  ws_host=PUB_WS_HOST,
-  rest_host=PUB_REST_HOST
-)
-
-now = int(round(time.time() * 1000))
-then = now - (1000 * 60 * 60 * 24 * 10) # 10 days ago
-
-async def log_historical_candles():
-  candles = await bfx.rest.get_public_candles('tBTCUSD', 0, then)
-  print ("Candles:")
-  [ print (c) for c in candles ]
-
-async def log_historical_trades():
-  trades = await bfx.rest.get_public_trades('tBTCUSD', 0, then)
-  print ("Trades:")
-  [ print (t) for t in trades ]
-
-async def log_books():
-  orders = await bfx.rest.get_public_books('tBTCUSD')
-  print ("Order book:")
-  [ print (o) for o in orders ]
-
-async def log_ticker():
-  ticker = await bfx.rest.get_public_ticker('tBTCUSD')
-  print ("Ticker:")
-  print (ticker)
-
-async def log_mul_tickers():
-  tickers = await bfx.rest.get_public_tickers(['tBTCUSD', 'tETHBTC'])
-  print ("Tickers:")
-  print (tickers)
-
-async def log_derivative_status():
-  status = await bfx.rest.get_derivative_status('tBTCF0:USTF0')
-  print ("Deriv status:")
-  print (status)
-
-async def run():
-  await log_historical_candles()
-  await log_historical_trades()
-  await log_books()
-  await log_ticker()
-  await log_mul_tickers()
-  await log_derivative_status()
-  
-t = asyncio.ensure_future(run())
-asyncio.get_event_loop().run_until_complete(t)
diff --git a/bfxapi/examples/rest/get_seed_trades.py b/bfxapi/examples/rest/get_seed_trades.py
deleted file mode 100644
index 6f9cbf80..00000000
--- a/bfxapi/examples/rest/get_seed_trades.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import os
-import sys
-import asyncio
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-
-# Retrieving seed trades requires public hosts
-bfx = Client(
-  logLevel='INFO',
-  ws_host=PUB_WS_HOST,
-  rest_host=PUB_REST_HOST
-)
-
-async def get_seeds():
-  candles = await bfx.rest.get_seed_candles('tBTCUSD')
-  print (candles)
-
-asyncio.get_event_loop().run_until_complete(get_seeds())
diff --git a/bfxapi/examples/rest/merchant.py b/bfxapi/examples/rest/merchant.py
deleted file mode 100644
index 54775887..00000000
--- a/bfxapi/examples/rest/merchant.py
+++ /dev/null
@@ -1,37 +0,0 @@
-import os
-import sys
-import asyncio
-sys.path.append('../../../')
-from bfxapi import Client
-from bfxapi.constants import WS_HOST, REST_HOST
-
-API_KEY=os.getenv("BFX_KEY")
-API_SECRET=os.getenv("BFX_SECRET")
-
-# Submitting invoices requires private hosts
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='DEBUG',
-  ws_host=WS_HOST,
-  rest_host=REST_HOST
-)
-
-async def run():
-  await bfx.rest.submit_invoice(amount='2.0', currency='USD', pay_currencies=['BTC', 'ETH'], order_id='order123', webhook='https://example.com/api/v3/order/order123',
-                                           redirect_url='https://example.com/api/v3/order/order123', customer_info_nationality='DE',
-                                           customer_info_resid_country='GB', customer_info_resid_city='London', customer_info_resid_zip_code='WC2H 7NA',
-                                           customer_info_resid_street='5-6 Leicester Square', customer_info_resid_building_no='23 A',
-                                           customer_info_full_name='John Doe', customer_info_email='john@example.com', duration=86339)
-
-  invoices = await bfx.rest.get_invoices()
-  print(invoices)
-
-  # await bfx.rest.complete_invoice(id=invoices[0]['id'], pay_ccy='BTC', deposit_id=1357996)
-
-  unlinked_deposits = await bfx.rest.get_unlinked_deposits(ccy='BTC')
-  print(unlinked_deposits)
-
-
-t = asyncio.ensure_future(run())
-asyncio.get_event_loop().run_until_complete(t)
diff --git a/bfxapi/examples/rest/transfer_wallet.py b/bfxapi/examples/rest/transfer_wallet.py
deleted file mode 100644
index a0c59ceb..00000000
--- a/bfxapi/examples/rest/transfer_wallet.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import os
-import sys
-import asyncio
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import WS_HOST, REST_HOST
-
-API_KEY=os.getenv("BFX_KEY")
-API_SECRET=os.getenv("BFX_SECRET")
-
-# Transfer wallet requires private hosts
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='DEBUG',
-  ws_host=WS_HOST,
-  rest_host=REST_HOST
-)
-
-async def transfer_wallet():
-  response = await bfx.rest.submit_wallet_transfer("exchange", "margin", "BTC", "BTC", 0.1)
-  # response is in the form of a Notification object
-  # notify_info is in the form of a Transfer object
-  print ("Transfer: ", response.notify_info)
-
-async def deposit_address():
-  response = await bfx.rest.get_wallet_deposit_address("exchange", "bitcoin")
-  # response is in the form of a Notification object
-  # notify_info is in the form of a DepositAddress object
-  print ("Address: ", response.notify_info)
-
-async def create_new_address():
-  response = await bfx.rest.create_wallet_deposit_address("exchange", "bitcoin")
-  # response is in the form of a Notification object
-  # notify_info is in the form of a DepositAddress object
-  print ("Address: ", response.notify_info)
-
-async def withdraw():
-  # tetheruse = Tether (ERC20)
-  response = await bfx.rest.submit_wallet_withdraw("exchange", "tetheruse", 5, "0xc5bbb852f82c24327693937d4012f496cff7eddf")
-  # response is in the form of a Notification object
-  # notify_info is in the form of a DepositAddress object
-  print ("Address: ", response.notify_info)
-
-async def run():
-  await transfer_wallet()
-  await deposit_address()
-  await withdraw()
-
-t = asyncio.ensure_future(run())
-asyncio.get_event_loop().run_until_complete(t)
diff --git a/bfxapi/examples/ws/cancel_order.py b/bfxapi/examples/ws/cancel_order.py
deleted file mode 100644
index e37ad00c..00000000
--- a/bfxapi/examples/ws/cancel_order.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import os
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client, Order
-from bfxapi.constants import WS_HOST, REST_HOST
-
-API_KEY=os.getenv("BFX_KEY")
-API_SECRET=os.getenv("BFX_SECRET")
-
-# Canceling orders requires private hosts
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='DEBUG',
-  ws_host=WS_HOST,
-  rest_host=REST_HOST
-)
-
-@bfx.ws.on('order_closed')
-def order_cancelled(order):
-  print ("Order cancelled.")
-  print (order)
-
-@bfx.ws.on('order_confirmed')
-async def trade_completed(order):
-  print ("Order confirmed.")
-  print (order)
-  await bfx.ws.cancel_order(order.id)
-
-@bfx.ws.on('error')
-def log_error(msg):
-  print ("Error: {}".format(msg))
-
-@bfx.ws.once('authenticated')
-async def submit_order(auth_message):
-  # create an initial order at a really low price so it stays open
-  await bfx.ws.submit_order('tBTCUSD', 10, 1, Order.Type.EXCHANGE_LIMIT)
-
-bfx.ws.run()
diff --git a/bfxapi/examples/ws/connect.py b/bfxapi/examples/ws/connect.py
deleted file mode 100644
index b63cd7d8..00000000
--- a/bfxapi/examples/ws/connect.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import os
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-
-bfx = Client(
-  logLevel='DEBUG',
-  ws_host=PUB_WS_HOST,
-  rest_host=PUB_REST_HOST
-)
-
-@bfx.ws.on('error')
-def log_error(msg):
-  print ("Error: {}".format(msg))
-
-@bfx.ws.on('all')
-async def log_output(output):
-  print ("WS: {}".format(output))
-
-bfx.ws.run()
diff --git a/bfxapi/examples/ws/connect_auth.py b/bfxapi/examples/ws/connect_auth.py
deleted file mode 100644
index a01c6d36..00000000
--- a/bfxapi/examples/ws/connect_auth.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import os
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import WS_HOST, REST_HOST
-
-API_KEY=os.getenv("BFX_KEY")
-API_SECRET=os.getenv("BFX_SECRET")
-
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='DEBUG',
-  ws_host=WS_HOST,
-  rest_host=REST_HOST,
-  dead_man_switch=True, # <-- kill all orders if this connection drops
-  channel_filter=['wallet'] # <-- only receive wallet updates
-)
-
-@bfx.ws.on('error')
-def log_error(msg):
-  print ("Error: {}".format(msg))
-
-@bfx.ws.on('authenticated')
-async def submit_order(auth_message):
-  print ("Authenticated!!")
diff --git a/bfxapi/examples/ws/full_orderbook.py b/bfxapi/examples/ws/full_orderbook.py
deleted file mode 100644
index baac6568..00000000
--- a/bfxapi/examples/ws/full_orderbook.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import sys
-import time
-from collections import OrderedDict
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-
-# Retrieving orderbook requires public hosts
-bfx = Client(
-  manageOrderBooks=True,
-  ws_host=PUB_WS_HOST,
-  rest_host=PUB_REST_HOST
-)
-
-class OrderBook:
-  def __init__(self, snapshot):
-    self.bids = OrderedDict()
-    self.asks = OrderedDict()
-    self.load(snapshot)
-
-  def load(self, snapshot):
-    for record in snapshot:
-      if record[2] >= 0:
-        self.bids[record[0]] = {
-          'count': record[1],
-          'amount': record[2]
-        }
-      else:
-        self.asks[record[0]] = {
-          'count': record[1],
-          'amount': record[2]
-        }
-
-  def update(self, record):
-    # count is 0
-    if record[1] == 0:
-      if record[2] == 1:
-        # remove from bids
-        del self.bids[record[0]]
-      elif record[2] == -1:
-        # remove from asks
-        del self.asks[record[0]]
-    elif record[1] > 0:
-      if record[2] > 0:
-        # update bids
-        if record[0] not in self.bids:
-          self.bids[record[0]] = {}
-        self.bids[record[0]]['count'] = record[1]
-        self.bids[record[0]]['amount'] = record[2]
-      elif record[2] < 0:
-        # update asks
-        if record[0] not in self.asks:
-          self.asks[record[0]] = {}
-        self.asks[record[0]]['count'] = record[1]
-        self.asks[record[0]]['amount'] = record[2]
-
-obs = {}
-
-@bfx.ws.on('error')
-def log_error(err):
-  print ("Error: {}".format(err))
-
-@bfx.ws.on('order_book_update')
-def log_update(data):
-  obs[data['symbol']].update(data['data'])
-
-@bfx.ws.on('order_book_snapshot')
-def log_snapshot(data):
-  obs[data['symbol']] = OrderBook(data['data'])
-
-async def start():
-  await bfx.ws.subscribe('book', 'tBTCUSD')
-
-bfx.ws.on('connected', start)
-bfx.ws.run()
-
-for n in range(0, 10):
-  time.sleep(2)
-  for key in obs:
-    print(f"Printing {key} orderbook...")
-    print(f"{obs[key].bids}\n")
-    print(f"{obs[key].asks}\n")
diff --git a/bfxapi/examples/ws/multiple_instances.py b/bfxapi/examples/ws/multiple_instances.py
deleted file mode 100644
index cf1dc047..00000000
--- a/bfxapi/examples/ws/multiple_instances.py
+++ /dev/null
@@ -1,180 +0,0 @@
-"""
-This is an example of how it is possible to spawn multiple
-bfx ws instances to comply with the open subscriptions number constraint (max. 25)
-
-(https://docs.bitfinex.com/docs/requirements-and-limitations)
-"""
-
-import sys
-sys.path.append('../../../')
-import asyncio
-from functools import partial
-import websockets as ws
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-import math
-import random
-
-MAX_CHANNELS = 25
-
-
-def get_random_list_of_tickers():
-    tickers = ["FILUST", "FTTUSD", "FTTUST", "FUNUSD", "GNOUSD", "GNTUSD", "GOTEUR", "GOTUSD", "GTXUSD", "ZRXUSD"]
-    return random.sample(tickers, 1)
-
-
-class Instance:
-    def __init__(self, _id):
-        self.id = _id
-        self.bfx = Client(logLevel='INFO', ws_host=PUB_WS_HOST, rest_host=PUB_REST_HOST)
-        self.subscriptions = {'trades': {}, 'ticker': {}}
-        self.is_ready = False
-
-    def run(self):
-        self.bfx.ws.run()
-        self.bfx.ws.on('error', log_error)
-        self.bfx.ws.on('new_trade', log_trade)
-        self.bfx.ws.on('new_ticker', log_ticker)
-        self.bfx.ws.on('subscribed', partial(on_subscribe, self))
-        self.bfx.ws.on('unsubscribed', partial(on_unsubscribed, self))
-        self.bfx.ws.on('connected', partial(on_connected, self))
-        self.bfx.ws.on('stopped', partial(on_stopped, self))
-
-    async def subscribe(self, symbols):
-        for symbol in symbols:
-            print(f'Subscribing to {symbol} channel')
-            await self.bfx.ws.subscribe_ticker(symbol)
-            await self.bfx.ws.subscribe_trades(symbol)
-            self.subscriptions['trades'][symbol] = None
-            self.subscriptions['ticker'][symbol] = None
-
-    async def unsubscribe(self, symbols):
-        for symbol in symbols:
-            if symbol in self.subscriptions['trades']:
-                print(f'Unsubscribing to {symbol} channel')
-                trades_ch_id = self.subscriptions['trades'][symbol]
-                ticker_ch_id = self.subscriptions['ticker'][symbol]
-                if trades_ch_id:
-                    await self.bfx.ws.unsubscribe(trades_ch_id)
-                else:
-                    del self.subscriptions['trades'][symbol]
-                if ticker_ch_id:
-                    await self.bfx.ws.unsubscribe(ticker_ch_id)
-                else:
-                    del self.subscriptions['ticker'][symbol]
-
-
-class Routine:
-    is_stopped = False
-
-    def __new__(cls, _loop, _ws, interval=1, start_delay=10):
-        instance = super().__new__(cls)
-        instance.interval = interval
-        instance.start_delay = start_delay
-        instance.ws = _ws
-        instance.task = _loop.create_task(instance.run())
-        return instance.task
-
-    async def run(self):
-        await asyncio.sleep(self.start_delay)
-        await self.do()
-        while True:
-            await asyncio.sleep(self.interval)
-            await self.do()
-
-    async def do(self):
-        subbed_tickers = get_all_subscriptions_tickers()
-        print(f'Subscribed tickers: {subbed_tickers}')
-
-        # if ticker is not in subbed tickers, then we subscribe to the channel
-        to_sub = [f"t{ticker}" for ticker in get_random_list_of_tickers() if f"t{ticker}" not in subbed_tickers]
-        for ticker in to_sub:
-            print(f'To subscribe: {ticker}')
-            instance = get_available_instance()
-            if instance and instance.is_ready:
-                print(f'Subscribing on instance {instance.id}')
-                await instance.subscribe([ticker])
-            else:
-                instances_to_create = math.ceil(len(to_sub) / MAX_CHANNELS)
-                create_instances(instances_to_create)
-                break
-
-        to_unsub = [f"t{ticker}" for ticker in subbed_tickers if f"t{ticker}" in get_random_list_of_tickers()]
-        if len(to_unsub) > 0:
-            print(f'To unsubscribe: {to_unsub}')
-            for instance in instances:
-                await instance.unsubscribe(to_unsub)
-
-    def stop(self):
-        self.task.cancel()
-        self.is_stopped = True
-
-
-instances = []
-
-
-def get_all_subscriptions_tickers():
-    tickers = []
-    for instance in instances:
-        for ticker in instance.subscriptions['trades']:
-            tickers.append(ticker)
-    return tickers
-
-
-def count_open_channels(instance):
-    return len(instance.subscriptions['trades']) + len(instance.subscriptions['ticker'])
-
-
-def create_instances(instances_to_create):
-    for _ in range(0, instances_to_create):
-        instance = Instance(len(instances))
-        instance.run()
-        instances.append(instance)
-
-
-def get_available_instance():
-    for instance in instances:
-        if count_open_channels(instance) + 1 <= MAX_CHANNELS:
-            return instance
-    return None
-
-
-def log_error(err):
-    print("Error: {}".format(err))
-
-
-def log_trade(trade):
-    print(trade)
-
-
-def log_ticker(ticker):
-    print(ticker)
-
-
-async def on_subscribe(instance, subscription):
-    print(f'Subscribed to {subscription.symbol} channel {subscription.channel_name}')
-    instance.subscriptions[subscription.channel_name][subscription.symbol] = subscription.chan_id
-
-
-async def on_unsubscribed(instance, subscription):
-    print(f'Unsubscribed to {subscription.symbol} channel {subscription.channel_name}')
-    instance.subscriptions[subscription.channel_name][subscription.symbol] = subscription.chan_id
-    del instance.subscriptions[subscription.channel_name][subscription.symbol]
-
-
-async def on_connected(instance):
-    print(f"Instance {instance.id} is connected")
-    instance.is_ready = True
-
-
-async def on_stopped(instance):
-    print(f"Instance {instance.id} is dead, removing it from instances list")
-    instances.pop(instance.id)
-
-
-def run():
-    loop = asyncio.get_event_loop()
-    task = Routine(loop, ws, interval=5)
-    loop.run_until_complete(task)
-
-run()
diff --git a/bfxapi/examples/ws/resubscribe_orderbook.py b/bfxapi/examples/ws/resubscribe_orderbook.py
deleted file mode 100644
index 5e102770..00000000
--- a/bfxapi/examples/ws/resubscribe_orderbook.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-
-# Retrieving orderbook requires public hosts
-bfx = Client(
-  manageOrderBooks=True,
-  ws_host=PUB_WS_HOST,
-  rest_host=PUB_REST_HOST
-)
-
-@bfx.ws.on('error')
-def log_error(err):
-  print ("Error: {}".format(err))
-
-@bfx.ws.on('unsubscribed')
-async def on_unsubscribe(subscription):
-  print ("Unsubscribed from {}".format(subscription.symbol))
-  # await subscription.subscribe()
-
-@bfx.ws.on('subscribed')
-async def on_subscribe(subscription):
-  print ("Subscribed to {}".format(subscription.symbol))
-  # await subscription.unsubscribe()
-  # or
-  # await bfx.ws.unsubscribe(subscription.chanId)
-
-@bfx.ws.once('subscribed')
-async def on_once_subscribe(subscription):
-  print ("Performig resubscribe")
-  await bfx.ws.resubscribe(subscription.chan_id)
-
-
-async def start():
-  await bfx.ws.subscribe('book', 'tBTCUSD')
-
-bfx.ws.on('connected', start)
-bfx.ws.run()
diff --git a/bfxapi/examples/ws/send_order.py b/bfxapi/examples/ws/send_order.py
deleted file mode 100644
index 743934d3..00000000
--- a/bfxapi/examples/ws/send_order.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import os
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client, Order
-from bfxapi.constants import WS_HOST, REST_HOST
-
-API_KEY=os.getenv("BFX_KEY")
-API_SECRET=os.getenv("BFX_SECRET")
-
-# Sending order requires private hosts
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='DEBUG',
-  ws_host=WS_HOST,
-  rest_host=REST_HOST
-)
-
-@bfx.ws.on('order_snapshot')
-async def cancel_all(data):
-  await bfx.ws.cancel_all_orders()
-
-@bfx.ws.on('order_confirmed')
-async def trade_completed(order):
-  print ("Order confirmed.")
-  print (order)
-  ## close the order
-  # await order.close()
-  # or
-  # await bfx.ws.cancel_order(order.id)
-  # or
-  # await bfx.ws.cancel_all_orders()
-
-@bfx.ws.on('error')
-def log_error(msg):
-  print ("Error: {}".format(msg))
-
-@bfx.ws.on('authenticated')
-async def submit_order(auth_message):
-  await bfx.ws.submit_order(symbol='tBTCUSD', price=None, amount=0.01, market_type=Order.Type.EXCHANGE_MARKET)
-
-# If you dont want to use a decorator
-# ws.on('authenticated', submit_order)
-# ws.on('error', log_error)
-
-# You can also provide a callback
-# await ws.submit_order('tBTCUSD', 0, 0.01,
-# 'EXCHANGE MARKET', onClose=trade_complete)
-
-bfx.ws.run()
diff --git a/bfxapi/examples/ws/start_stop_connection.py b/bfxapi/examples/ws/start_stop_connection.py
deleted file mode 100644
index 4ddafbfd..00000000
--- a/bfxapi/examples/ws/start_stop_connection.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-
-bfx = Client(
-  logLevel='DEBUG',
-  ws_host=PUB_WS_HOST,
-  rest_host=PUB_REST_HOST
-)
-
-@bfx.ws.on('order_book_snapshot')
-async def log_snapshot(data):
-  print ("Snapshot: {}".format(data))
-  # stop the websocket once a snapshot is received
-  await bfx.ws.stop()
-
-async def start():
-  await bfx.ws.subscribe('book', 'tBTCUSD')
-
-bfx.ws.on('connected', start)
-bfx.ws.run()
diff --git a/bfxapi/examples/ws/subscribe_derivative_status.py b/bfxapi/examples/ws/subscribe_derivative_status.py
deleted file mode 100644
index 3c5a9954..00000000
--- a/bfxapi/examples/ws/subscribe_derivative_status.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-
-# Retrieving derivative status requires public hosts
-bfx = Client(
-  logLevel='DEBUG',
-  ws_host=PUB_WS_HOST,
-  rest_host=PUB_REST_HOST
-)
-
-@bfx.ws.on('error')
-def log_error(err):
-  print ("Error: {}".format(err))
-
-@bfx.ws.on('status_update')
-def log_msg(msg):
-  print (msg)
-
-async def start():
-  await bfx.ws.subscribe_derivative_status('tBTCF0:USTF0')
-
-bfx.ws.on('connected', start)
-bfx.ws.run()
diff --git a/bfxapi/examples/ws/subscribe_orderbook.py b/bfxapi/examples/ws/subscribe_orderbook.py
deleted file mode 100644
index a9c44ab1..00000000
--- a/bfxapi/examples/ws/subscribe_orderbook.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import os
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-
-# Retrieving trades/candles requires public hosts
-bfx = Client(
-  logLevel='DEBUG',
-  ws_host=PUB_WS_HOST,
-  rest_host=PUB_REST_HOST,
-  # Verifies that the local orderbook is up to date
-  # with the bitfinex servers
-  manageOrderBooks=True
-)
-
-@bfx.ws.on('error')
-def log_error(err):
-  print ("Error: {}".format(err))
-
-@bfx.ws.on('order_book_update')
-def log_update(data):
-  print ("Book update: {}".format(data))
-
-@bfx.ws.on('order_book_snapshot')
-def log_snapshot(data):
-  print ("Initial book: {}".format(data))
-
-async def start():
-  await bfx.ws.subscribe('book', 'tBTCUSD')
-  # bfx.ws.subscribe('book', 'tETHUSD')
-
-bfx.ws.on('connected', start)
-bfx.ws.run()
diff --git a/bfxapi/examples/ws/subscribe_tickers.py b/bfxapi/examples/ws/subscribe_tickers.py
deleted file mode 100644
index 616571ed..00000000
--- a/bfxapi/examples/ws/subscribe_tickers.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-
-# Retrieving tickers requires public hosts
-bfx = Client(
-  logLevel='DEBUG',
-  ws_host=PUB_WS_HOST,
-  rest_host=PUB_REST_HOST
-)
-
-@bfx.ws.on('error')
-def log_error(err):
-  print ("Error: {}".format(err))
-
-@bfx.ws.on('new_funding_ticker')
-def log_ticker(ticker):
-  print ("New ticker: {}".format(ticker))
-
-async def start():
-  await bfx.ws.subscribe('ticker', 'fUSD')
-
-bfx.ws.on('connected', start)
-bfx.ws.run()
diff --git a/bfxapi/examples/ws/subscribe_trades_candles.py b/bfxapi/examples/ws/subscribe_trades_candles.py
deleted file mode 100644
index 024eb587..00000000
--- a/bfxapi/examples/ws/subscribe_trades_candles.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client
-from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
-
-# Retrieving trades/candles requires public hosts
-bfx = Client(
-  logLevel='DEBUG',
-  ws_host=PUB_WS_HOST,
-  rest_host=PUB_REST_HOST
-)
-
-@bfx.ws.on('error')
-def log_error(err):
-  print ("Error: {}".format(err))
-
-@bfx.ws.on('new_candle')
-def log_candle(candle):
-  print ("New candle: {}".format(candle))
-
-@bfx.ws.on('new_trade')
-def log_trade(trade):
-  print ("New trade: {}".format(trade))
-
-@bfx.ws.on('new_user_trade')
-def log_user_trade(trade):
-  print ("New user trade: {}".format(trade))
-
-async def start():
-  await bfx.ws.subscribe('candles', 'tBTCUSD', timeframe='1m')
-  await bfx.ws.subscribe('trades', 'tBTCUSD')
-
-bfx.ws.on('connected', start)
-bfx.ws.run()
diff --git a/bfxapi/examples/ws/update_order.py b/bfxapi/examples/ws/update_order.py
deleted file mode 100644
index 8eb26ae4..00000000
--- a/bfxapi/examples/ws/update_order.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import os
-import sys
-sys.path.append('../../../')
-
-from bfxapi import Client, Order
-from bfxapi.constants import WS_HOST, REST_HOST
-
-API_KEY=os.getenv("BFX_KEY")
-API_SECRET=os.getenv("BFX_SECRET")
-
-# Update order requires private hosts
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='INFO',
-  ws_host=WS_HOST,
-  rest_host=REST_HOST
-)
-
-@bfx.ws.on('order_update')
-def order_updated(order):
-  print ("Order updated.")
-  print (order)
-
-@bfx.ws.once('order_update')
-async def order_once_updated(order):
-  # update a second time using the object function
-  await order.update(price=80, amount=0.02, flags="2nd update")
-
-@bfx.ws.once('order_confirmed')
-async def trade_completed(order):
-  print ("Order confirmed.")
-  print (order)
-  await bfx.ws.update_order(order.id, price=100, amount=0.01)
-
-@bfx.ws.on('error')
-def log_error(msg):
-  print ("Error: {}".format(msg))
-
-@bfx.ws.once('authenticated')
-async def submit_order(auth_message):
-  # create an inital order a really low price so it stays open
-  await bfx.ws.submit_order('tBTCUSD', 10, 1, Order.Type.EXCHANGE_LIMIT)
-
-bfx.ws.run()
diff --git a/bfxapi/examples/ws/wallet_balance.py b/bfxapi/examples/ws/wallet_balance.py
deleted file mode 100644
index f4af1636..00000000
--- a/bfxapi/examples/ws/wallet_balance.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import os
-import sys
-sys.path.append('../../../')
-from bfxapi import Client
-from bfxapi.constants import WS_HOST, REST_HOST
-
-API_KEY=os.getenv("BFX_KEY")
-API_SECRET=os.getenv("BFX_SECRET")
-
-# Checking wallet balances requires private hosts
-bfx = Client(
-  API_KEY=API_KEY,
-  API_SECRET=API_SECRET,
-  logLevel='INFO',
-  ws_host=WS_HOST,
-  rest_host=REST_HOST
-)
-
-@bfx.ws.on('wallet_snapshot')
-def log_snapshot(wallets):
-  for wallet in wallets:
-    print (wallet)
-  
-  # or bfx.ws.wallets.get_wallets()
-
-@bfx.ws.on('wallet_update')
-def log_update(wallet):
-  print ("Balance updates: {}".format(wallet))
-
-@bfx.ws.on('error')
-def log_error(msg):
-  print ("Error: {}".format(msg))
-
-bfx.ws.run()
diff --git a/bfxapi/exceptions.py b/bfxapi/exceptions.py
new file mode 100644
index 00000000..f7723c3b
--- /dev/null
+++ b/bfxapi/exceptions.py
@@ -0,0 +1,16 @@
+__all__ = [
+    "BfxBaseException",
+
+    "LabelerSerializerException",
+]
+
+class BfxBaseException(Exception):
+    """
+    Base class for every custom exception in bfxapi/rest/exceptions.py and bfxapi/websocket/exceptions.py.
+    """
+
+class LabelerSerializerException(BfxBaseException):
+    """
+    This exception indicates an error thrown by the _Serializer class in bfxapi/labeler.py.
+    """
+    
\ No newline at end of file
diff --git a/bfxapi/labeler.py b/bfxapi/labeler.py
new file mode 100644
index 00000000..dfb48815
--- /dev/null
+++ b/bfxapi/labeler.py
@@ -0,0 +1,97 @@
+from typing import Type, Generic, TypeVar, Iterable, Dict, List, Tuple, Any, cast
+
+from .exceptions import LabelerSerializerException
+
+T = TypeVar("T", bound="_Type")
+
+def compose(*decorators):
+    def wrapper(function):
+        for decorator in reversed(decorators):
+            function = decorator(function)
+        return function
+
+    return wrapper
+
+def partial(cls):
+    def __init__(self, **kwargs):
+        for annotation in self.__annotations__.keys():
+            if annotation not in kwargs:
+                self.__setattr__(annotation, None)
+            else: self.__setattr__(annotation, kwargs[annotation])
+
+            kwargs.pop(annotation, None)
+
+        if len(kwargs) != 0:
+            raise TypeError(f"{cls.__name__}.__init__() got an unexpected keyword argument '{list(kwargs.keys())[0]}'")
+
+    cls.__init__ = __init__
+
+    return cls
+
+class _Type:
+    """
+    Base class for any dataclass serializable by the _Serializer generic class.
+    """
+
+class _Serializer(Generic[T]):
+    def __init__(self, name: str, klass: Type[_Type], labels: List[str],
+                 *, flat: bool = False, ignore: List[str] = [ "_PLACEHOLDER" ]):
+        self.name, self.klass, self.__labels, self.__flat, self.__ignore = name, klass, labels, flat, ignore
+
+    def _serialize(self, *args: Any) -> Iterable[Tuple[str, Any]]:
+        if self.__flat:
+            args = tuple(_Serializer.__flatten(list(args)))
+
+        if len(self.__labels) > len(args):
+            raise LabelerSerializerException(f"{self.name} -> <labels> and <*args> " \
+                "arguments should contain the same amount of elements.")
+
+        for index, label in enumerate(self.__labels):
+            if label not in self.__ignore:
+                yield label, args[index]
+
+    def parse(self, *values: Any) -> T:
+        return cast(T, self.klass(**dict(self._serialize(*values))))
+
+    def get_labels(self) -> List[str]:
+        return [ label for label in self.__labels if label not in self.__ignore ]
+
+    @classmethod
+    def __flatten(cls, array: List[Any]) -> List[Any]:
+        if len(array) == 0:
+            return array
+
+        if isinstance(array[0], list):
+            return cls.__flatten(array[0]) + cls.__flatten(array[1:])
+
+        return array[:1] + cls.__flatten(array[1:])
+
+class _RecursiveSerializer(_Serializer, Generic[T]):
+    def __init__(self, name: str, klass: Type[_Type], labels: List[str],
+                 *, serializers: Dict[str, _Serializer[Any]],
+                     flat: bool = False, ignore: List[str] = [ "_PLACEHOLDER" ]):
+        super().__init__(name, klass, labels, flat=flat, ignore=ignore)
+
+        self.serializers = serializers
+
+    def parse(self, *values: Any) -> T:
+        serialization = dict(self._serialize(*values))
+
+        for key in serialization:
+            if key in self.serializers.keys():
+                serialization[key] = self.serializers[key].parse(*serialization[key])
+
+        return cast(T, self.klass(**serialization))
+
+def generate_labeler_serializer(name: str, klass: Type[T], labels: List[str],
+                                *, flat: bool = False, ignore: List[str] = [ "_PLACEHOLDER" ]
+                               ) -> _Serializer[T]:
+    return _Serializer[T](name, klass, labels, \
+        flat=flat, ignore=ignore)
+
+def generate_recursive_serializer(name: str, klass: Type[T], labels: List[str],
+                                  *, serializers: Dict[str, _Serializer[Any]],
+                                      flat: bool = False, ignore: List[str] = [ "_PLACEHOLDER" ]
+                                 ) -> _RecursiveSerializer[T]:
+    return _RecursiveSerializer[T](name, klass, labels, \
+        serializers=serializers, flat=flat, ignore=ignore)
diff --git a/bfxapi/models/__init__.py b/bfxapi/models/__init__.py
deleted file mode 100644
index c7bf7b25..00000000
--- a/bfxapi/models/__init__.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""
-This module contains a group of different models which
-are used to define data types
-"""
-
-from .order import Order
-from .trade import Trade
-from .order_book import OrderBook
-from .subscription import Subscription
-from .wallet import Wallet
-from .position import Position
-from .funding_loan import FundingLoan
-from .funding_offer import FundingOffer
-from .funding_credit import FundingCredit
-from .notification import Notification
-from .transfer import Transfer
-from .deposit_address import DepositAddress
-from .withdraw import Withdraw
-from .ticker import Ticker
-from .funding_ticker import FundingTicker
-from .ledger import Ledger
-from .funding_trade import FundingTrade
-from .margin_info import MarginInfo
-from .margin_info_base import MarginInfoBase
-from .movement import Movement
-
-NAME = "models"
diff --git a/bfxapi/models/deposit_address.py b/bfxapi/models/deposit_address.py
deleted file mode 100644
index c259b18b..00000000
--- a/bfxapi/models/deposit_address.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
-Module used to describe a DepositAddress object
-"""
-
-class DepositModel:
-    """
-    Enum used to index the location of each value in a raw array
-    """
-    METHOD = 1
-    CURRENCY = 2
-    ADDRESS = 4
-
-class DepositAddress:
-    """
-    [None, 'BITCOIN', 'BTC', None, '38zsUkv8q2aiXK9qsZVwepXjWeh3jKvvZw']
-
-    METHOD string  Protocol used for funds transfer
-    SYMBOL string  Currency symbol
-    ADDRESS string  Deposit address for funds transfer
-    """
-
-    def __init__(self, method, currency, address):
-        self.method = method
-        self.currency = currency
-        self.address = address
-
-    @staticmethod
-    def from_raw_deposit_address(raw_add):
-        """
-        Parse a raw deposit object into a DepositAddress object
-
-        @return DepositAddress
-        """
-        method = raw_add[DepositModel.METHOD]
-        currency = raw_add[DepositModel.CURRENCY]
-        address = raw_add[DepositModel.ADDRESS]
-        return DepositAddress(method, currency, address)
-
-    def __str__(self):
-        """
-        Allow us to print the Transfer object in a pretty format
-        """
-        text = "DepositAddress <{}  method={} currency={}>"
-        return text.format(self.address, self.method, self.currency)
diff --git a/bfxapi/models/funding_credit.py b/bfxapi/models/funding_credit.py
deleted file mode 100644
index 0f9c9a35..00000000
--- a/bfxapi/models/funding_credit.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-
-class FundingCreditModel:
-    """
-    Enum used to index the location of each value in a raw array
-    """
-    ID = 0
-    SYMBOL = 1
-    SIDE = 2
-    MTS_CREATE = 3
-    MTS_UPDATE = 4
-    AMOUNT = 5
-    FLAGS = 6
-    STATUS = 7
-    RATE = 11
-    PERIOD = 12
-    MTS_OPENING = 13
-    MTS_LAST_PAYOUT = 14
-    NOTIFY = 15
-    HIDDEN = 16
-    RENEW = 18
-    NO_CLOSE = 20
-    POSITION_PAIR = 21
-
-
-class FundingCredit:
-    """
-    ID	integer	Offer ID
-    SYMBOL	string	The currency of the offer (fUSD, etc)
-    SIDE	string	"Lend" or "Loan"
-    MTS_CREATE	int	Millisecond Time Stamp when the offer was created
-    MSG_UPDATE	int	Millisecond Time Stamp when the offer was updated
-    AMOUNT	float	Amount the offer is for
-    FLAGS	object	future params object (stay tuned)
-    STATUS	string	Offer Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED
-    RATE	float	Rate of the offer
-    PERIOD	int	Period of the offer
-    MTS_OPENING	int	Millisecond Time Stamp when funding opened
-    MTS_LAST_PAYOUT	int	Millisecond Time Stamp when last payout received
-    NOTIFY	int	0 if false, 1 if true
-    HIDDEN	int	0 if false, 1 if true
-    RENEW	int	0 if false, 1 if true
-    NO_CLOSE	int	0 if false, 1 if true Whether the funding will be closed when the
-              position is closed
-    POSITION_PAIR	string	Pair of the position that the funding was used for
-    """
-
-    def __init__(self, fid, symbol, side, mts_create, mts_update, amount, flags, status, rate,
-                 period, mts_opening, mts_last_payout, notify, hidden, renew, no_close,
-                 position_pair):
-        # pylint: disable=invalid-name
-        self.id = fid
-        self.symbol = symbol
-        self.side = side
-        self.mts_create = mts_create
-        self.mts_update = mts_update
-        self.amount = amount
-        self.flags = flags
-        self.status = status
-        self.rate = rate
-        self.period = period
-        self.mts_opening = mts_opening
-        self.mts_last_payout = mts_last_payout
-        self.notify = notify
-        self.hidden = hidden
-        self.renew = renew
-        self.no_close = no_close
-        self.position_pair = position_pair
-
-    @staticmethod
-    def from_raw_credit(raw_credit):
-        """
-        Parse a raw credit object into a FundingCredit object
-
-        @return FundingCredit
-        """
-        fid = raw_credit[FundingCreditModel.ID]
-        symbol = raw_credit[FundingCreditModel.SYMBOL]
-        side = raw_credit[FundingCreditModel.SIDE]
-        mts_create = raw_credit[FundingCreditModel.MTS_CREATE]
-        mts_update = raw_credit[FundingCreditModel.MTS_UPDATE]
-        amount = raw_credit[FundingCreditModel.AMOUNT]
-        flags = raw_credit[FundingCreditModel.FLAGS]
-        status = raw_credit[FundingCreditModel.STATUS]
-        rate = raw_credit[FundingCreditModel.RATE]
-        period = raw_credit[FundingCreditModel.PERIOD]
-        mts_opening = raw_credit[FundingCreditModel.MTS_OPENING]
-        mts_last_payout = raw_credit[FundingCreditModel.MTS_LAST_PAYOUT]
-        notify = raw_credit[FundingCreditModel.NOTIFY]
-        hidden = raw_credit[FundingCreditModel.HIDDEN]
-        renew = raw_credit[FundingCreditModel.RENEW]
-        no_close = raw_credit[FundingCreditModel.NO_CLOSE]
-        position_pair = raw_credit[FundingCreditModel.POSITION_PAIR]
-        return FundingCredit(fid, symbol, side, mts_create, mts_update, amount,
-                             flags, status, rate, period, mts_opening, mts_last_payout,
-                             notify, hidden, renew, no_close, position_pair)
-
-    def __str__(self):
-        string = "FundingCredit '{}' <id={} rate={} amount={} period={} status='{}'>"
-        return string.format(self.symbol, self.id, self.rate, self.amount,
-                             self.period, self.status)
diff --git a/bfxapi/models/funding_loan.py b/bfxapi/models/funding_loan.py
deleted file mode 100644
index ebb75370..00000000
--- a/bfxapi/models/funding_loan.py
+++ /dev/null
@@ -1,96 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-
-class FundingLoanModel:
-    """
-    Enum used to index the location of each value in a raw array
-    """
-    ID = 0
-    SYMBOL = 1
-    SIDE = 2
-    MTS_CREATE = 3
-    MTS_UPDATE = 4
-    AMOUNT = 5
-    FLAGS = 6
-    STATUS = 7
-    RATE = 11
-    PERIOD = 12
-    MTS_OPENING = 13
-    MTS_LAST_PAYOUT = 14
-    NOTIFY = 15
-    HIDDEN = 16
-    RENEW = 18
-    NO_CLOSE = 20
-
-
-class FundingLoan:
-    """
-    ID	integer	Offer ID
-    SYMBOL	string	The currency of the offer (fUSD, etc)
-    SIDE	string	"Lend" or "Loan"
-    MTS_CREATE	int	Millisecond Time Stamp when the offer was created
-    MTS_UPDATE	int	Millisecond Time Stamp when the offer was created
-    AMOUNT	float	Amount the offer is for
-    FLAGS	object	future params object (stay tuned)
-    STATUS	string	Offer Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED
-    RATE	float	Rate of the offer
-    PERIOD	int	Period of the offer
-    MTS_OPENING	int	Millisecond Time Stamp for when the loan was opened
-    MTS_LAST_PAYOUT	int	Millisecond Time Stamp for when the last payout was made
-    NOTIFY	int	0 if false, 1 if true
-    HIDDEN	int	0 if false, 1 if true
-    RENEW	int	0 if false, 1 if true
-    NO_CLOSE	int	If funding will be returned when position is closed. 0 if false, 1 if true
-    """
-
-    def __init__(self, fid, symbol, side, mts_create, mts_update, amount, flags, status, rate,
-                 period, mts_opening, mts_last_payout, notify, hidden, renew, no_close):
-        # pylint: disable=invalid-name
-        self.id = fid
-        self.symbol = symbol
-        self.side = side
-        self.mts_create = mts_create
-        self.mts_update = mts_update
-        self.amount = amount
-        self.flags = flags
-        self.status = status
-        self.rate = rate
-        self.period = period
-        self.mts_opening = mts_opening
-        self.mts_last_payout = mts_last_payout
-        self.notify = notify
-        self.hidden = hidden
-        self.renew = renew
-        self.no_close = no_close
-
-    @staticmethod
-    def from_raw_loan(raw_loan):
-        """
-        Parse a raw funding load into a FundingLoan object
-
-        @return FundingLoan
-        """
-        fid = raw_loan[FundingLoanModel.ID]
-        symbol = raw_loan[FundingLoanModel.SYMBOL]
-        side = raw_loan[FundingLoanModel.SIDE]
-        mts_create = raw_loan[FundingLoanModel.MTS_CREATE]
-        mts_update = raw_loan[FundingLoanModel.MTS_UPDATE]
-        amount = raw_loan[FundingLoanModel.AMOUNT]
-        flags = raw_loan[FundingLoanModel.FLAGS]
-        status = raw_loan[FundingLoanModel.STATUS]
-        rate = raw_loan[FundingLoanModel.RATE]
-        period = raw_loan[FundingLoanModel.PERIOD]
-        mts_opening = raw_loan[FundingLoanModel.MTS_OPENING]
-        mts_last_payout = raw_loan[FundingLoanModel.MTS_LAST_PAYOUT]
-        notify = raw_loan[FundingLoanModel.NOTIFY]
-        hidden = raw_loan[FundingLoanModel.HIDDEN]
-        renew = raw_loan[FundingLoanModel.RENEW]
-        no_close = raw_loan[FundingLoanModel.NO_CLOSE]
-        return FundingLoan(fid, symbol, side, mts_create, mts_update, amount, flags, status, rate,
-                           period, mts_opening, mts_last_payout, notify, hidden, renew, no_close)
-
-    def __str__(self):
-        return "FundingLoan '{}' <id={} rate={} amount={} period={} status='{}'>".format(
-            self.symbol, self.id, self.rate, self.amount, self.period, self.status)
diff --git a/bfxapi/models/funding_offer.py b/bfxapi/models/funding_offer.py
deleted file mode 100644
index d848b638..00000000
--- a/bfxapi/models/funding_offer.py
+++ /dev/null
@@ -1,96 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-class FundingOfferTypes:
-    """
-    Enum used to define the different funding offer types
-    """
-    LIMIT = 'LIMIT'
-    FRR_DELTA = 'FRRDELTAVAR'
-
-class FundingOfferModel:
-    """
-    Enum used to index the location of each value in a raw array
-    """
-    ID = 0
-    SYMBOL = 1
-    MTS_CREATE = 2
-    MTS_UPDATED = 3
-    AMOUNT = 4
-    AMOUNT_ORIG = 5
-    TYPE = 6
-    FLAGS = 9
-    STATUS = 10
-    RATE = 14
-    PERIOD = 15
-    NOTFIY = 16
-    HIDDEN = 17
-    RENEW = 19
-
-
-class FundingOffer:
-    """
-    ID	integer	Offer ID
-    SYMBOL	string	The currency of the offer (fUSD, etc)
-    MTS_CREATED	int	Millisecond Time Stamp when the offer was created
-    MSG_UPDATED	int	Millisecond Time Stamp when the offer was created
-    AMOUNT	float	Amount the offer is for
-    AMOUNT_ORIG	float	Amount the offer was entered with originally
-    TYPE	string	"lend" or "loan"
-    FLAGS	object	future params object (stay tuned)
-    STATUS	string	Offer Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED
-    RATE	float	Rate of the offer
-    PERIOD	int	Period of the offer
-    NOTIFY	int	0 if false, 1 if true
-    HIDDEN	int	0 if false, 1 if true
-    RENEW	int	0 if false, 1 if true
-    """
-
-    Type = FundingOfferTypes()
-
-    def __init__(self, fid, symbol, mts_create, mts_updated, amount, amount_orig, f_type,
-                 flags, status, rate, period, notify, hidden, renew):
-        # pylint: disable=invalid-name
-        self.id = fid
-        self.symbol = symbol
-        self.mts_create = mts_create
-        self.mts_updated = mts_updated
-        self.amount = amount
-        self.amount_orig = amount_orig
-        self.f_type = f_type
-        self.flags = flags
-        self.status = status
-        self.rate = rate
-        self.period = period
-        self.notify = notify
-        self.hidden = hidden
-        self.renew = renew
-
-    @staticmethod
-    def from_raw_offer(raw_offer):
-        """
-        Parse a raw funding offer into a RawFunding object
-
-        @return FundingOffer
-        """
-        oid = raw_offer[FundingOfferModel.ID]
-        symbol = raw_offer[FundingOfferModel.SYMBOL]
-        mts_create = raw_offer[FundingOfferModel.MTS_CREATE]
-        mts_updated = raw_offer[FundingOfferModel.MTS_UPDATED]
-        amount = raw_offer[FundingOfferModel.AMOUNT]
-        amount_orig = raw_offer[FundingOfferModel.AMOUNT_ORIG]
-        f_type = raw_offer[FundingOfferModel.TYPE]
-        flags = raw_offer[FundingOfferModel.FLAGS]
-        status = raw_offer[FundingOfferModel.STATUS]
-        rate = raw_offer[FundingOfferModel.RATE]
-        period = raw_offer[FundingOfferModel.PERIOD]
-        notify = raw_offer[FundingOfferModel.NOTFIY]
-        hidden = raw_offer[FundingOfferModel.HIDDEN]
-        renew = raw_offer[FundingOfferModel.RENEW]
-        return FundingOffer(oid, symbol, mts_create, mts_updated, amount,
-                            amount_orig, f_type, flags, status, rate, period, notify, hidden, renew)
-
-    def __str__(self):
-        return "FundingOffer '{}' <id={} rate={} period={} status='{}'>".format(
-            self.symbol, self.id, self.rate, self.period, self.status)
diff --git a/bfxapi/models/funding_ticker.py b/bfxapi/models/funding_ticker.py
deleted file mode 100644
index 983f8adc..00000000
--- a/bfxapi/models/funding_ticker.py
+++ /dev/null
@@ -1,93 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-class FundingTickerModel:
-    """
-    Enum used to index the different values in a raw funding ticker array
-    """
-    FRR = 0
-    BID = 1
-    BID_PERIOD = 2
-    BID_SIZE = 3
-    ASK = 4
-    ASK_PERIOD = 5
-    ASK_SIZE = 6
-    DAILY_CHANGE = 7
-    DAILY_CHANGE_PERC = 8
-    LAST_PRICE = 9
-    VOLUME = 10
-    HIGH = 11
-    LOW = 12
-    # _PLACEHOLDER,
-    # _PLACEHOLDER,
-    FRR_AMOUNT_AVAILABLE = 15
-
-class FundingTicker:
-    """
-    FRR	        float	Flash Return Rate - average of all fixed rate funding over the last hour
-                (funding tickers only)
-    BID	        float	Price of last highest bid
-    BID_PERIOD	int	Bid period covered in days (funding tickers only)
-    BID_SIZE	float	Sum of the 25 highest bid sizes
-    ASK	float	Price of last lowest ask
-    ASK_PERIOD	int	Ask period covered in days (funding tickers only)
-    ASK_SIZE	float	Sum of the 25 lowest ask sizes
-    DAILY_CHANGE	float	Amount that the last price has changed since yesterday
-    DAILY_CHANGE_RELATIVE	float	Relative price change since yesterday
-                            (*100 for percentage change)
-    LAST_PRICE	float	Price of the last trade
-    VOLUME	    float	Daily volume
-    HIGH	    float	Daily high
-    LOW	        float	Daily low
-    FRR_AMOUNT_AVAILABLE	float	The amount of funding that is available at the
-                Flash Return Rate (funding tickers only)
-    """
-
-    def __init__(self, pair, frr, bid, bid_period, bid_size, ask, ask_period, ask_size,
-                 daily_change, daily_change_perc, last_price, volume, high, low, frr_amount_avail):
-        self.pair = pair
-        self.frr = frr
-        self.bid = bid
-        self.bid_period = bid_period
-        self.bid_size = bid_size
-        self.ask = ask
-        self.ask_period = ask_period
-        self.ask_size = ask_size
-        self.daily_change = daily_change
-        self.daily_change_perc = daily_change_perc
-        self.last_price = last_price
-        self.volume = volume
-        self.high = high
-        self.low = low
-        self.frr_amount_available = frr_amount_avail
-
-    @staticmethod
-    def from_raw_ticker(raw_ticker, pair):
-        """
-        Generate a Ticker object from a raw ticker array
-        """
-        # [72128,[6914.5,28.123061460000002,6914.6,22.472037289999996,175.8,0.0261,6915.7,
-        # 6167.26141685,6964.2,6710.8]]
-
-        return FundingTicker(
-            pair,
-            raw_ticker[FundingTickerModel.FRR],
-            raw_ticker[FundingTickerModel.BID],
-            raw_ticker[FundingTickerModel.BID_PERIOD],
-            raw_ticker[FundingTickerModel.BID_SIZE],
-            raw_ticker[FundingTickerModel.ASK],
-            raw_ticker[FundingTickerModel.ASK_PERIOD],
-            raw_ticker[FundingTickerModel.ASK_SIZE],
-            raw_ticker[FundingTickerModel.DAILY_CHANGE],
-            raw_ticker[FundingTickerModel.DAILY_CHANGE_PERC],
-            raw_ticker[FundingTickerModel.LAST_PRICE],
-            raw_ticker[FundingTickerModel.VOLUME],
-            raw_ticker[FundingTickerModel.HIGH],
-            raw_ticker[FundingTickerModel.LOW],
-            raw_ticker[FundingTickerModel.FRR_AMOUNT_AVAILABLE]
-        )
-
-    def __str__(self):
-        return "FundingTicker '{}' <last='{}' volume={}>".format(
-            self.pair, self.last_price, self.volume)
diff --git a/bfxapi/models/funding_trade.py b/bfxapi/models/funding_trade.py
deleted file mode 100644
index 07f6ea52..00000000
--- a/bfxapi/models/funding_trade.py
+++ /dev/null
@@ -1,55 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-class FundingTradeModel:
-    """
-    Enum used to index the different values in a raw funding trade array
-    """
-    ID = 0
-    SYMBOL = 1
-    MTS_CREATE = 2
-    OFFER_ID = 3
-    AMOUNT = 4
-    RATE = 5
-    PERIOD = 6
-
-class FundingTrade:
-    """
-    ID	integer	Offer ID
-    SYMBOL	string	The currency of the offer (fUSD, etc)
-    MTS_CREATE	int	Millisecond Time Stamp when the offer was created
-    OFFER_ID	int	The ID of the offer
-    AMOUNT	float	Amount the offer is for
-    RATE	float	Rate of the offer
-    PERIOD	int	Period of the offer
-    """
-
-    def __init__(self, tid, symbol, mts_create, offer_id, amount, rate, period):
-        self.tid = tid
-        self.symbol = symbol
-        self.mts_create = mts_create
-        self.offer_id = offer_id
-        self.amount = amount
-        self.rate = rate
-        self.period = period
-
-    @staticmethod
-    def from_raw_rest_trade(raw_trade):
-        """
-        Generate a Ticker object from a raw ticker array
-        """
-        # [[636040,"fUST",1574077528000,41237922,-100,0.0024,2,null]]
-        return FundingTrade(
-            raw_trade[FundingTradeModel.ID],
-            raw_trade[FundingTradeModel.SYMBOL],
-            raw_trade[FundingTradeModel.MTS_CREATE],
-            raw_trade[FundingTradeModel.OFFER_ID],
-            raw_trade[FundingTradeModel.AMOUNT],
-            raw_trade[FundingTradeModel.RATE],
-            raw_trade[FundingTradeModel.PERIOD]
-        )
-
-    def __str__(self):
-        return "FundingTrade '{}' x {} @ {} for {} days".format(
-            self.symbol, self.amount, self.rate, self.period)
diff --git a/bfxapi/models/ledger.py b/bfxapi/models/ledger.py
deleted file mode 100644
index 766a9656..00000000
--- a/bfxapi/models/ledger.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""
-Module used to describe a ledger object
-"""
-
-class LedgerModel:
-    """
-    Enum used to index the location of each value in a raw array
-    """
-    ID = 0
-    CURRENCY = 1
-    MTS = 3
-    AMOUNT = 5
-    BALANCE = 6
-    DESCRIPTION = 8
-
-class Ledger:
-    """
-    ID int
-    CURRENCY string Currency (BTC, etc)
-    PLACEHOLDER
-    MTS	int	Millisecond Time Stamp of the update
-    PLACEHOLDER
-    AMOUNT string  Amount of funds to ledger
-    BALANCE string  Amount of funds to ledger
-    PLACEHOLDER
-    DESCRIPTION
-    """
-
-    def __init__(self, lid, currency, mts, amount, balance, description):
-        self.id = lid
-        self.currency = currency
-        self.mts = mts
-        self.amount = amount
-        self.balance = balance
-        self.description = description
-
-    @staticmethod
-    def from_raw_ledger(raw_ledger):
-        """
-        Parse a raw ledger object into a Ledger object
-
-        @return Ledger
-        """
-        lid = raw_ledger[LedgerModel.ID]
-        currency = raw_ledger[LedgerModel.CURRENCY]
-        mts = raw_ledger[LedgerModel.MTS]
-        amount = raw_ledger[LedgerModel.AMOUNT]
-        balance = raw_ledger[LedgerModel.BALANCE]
-        description = raw_ledger[LedgerModel.DESCRIPTION]
-        return Ledger(lid, currency, mts, amount, balance, description)
-
-    def __str__(self):
-        ''' Allow us to print the Ledger object in a pretty format '''
-        text = "Ledger <{} {} {} balance:{} '{}' mts={}>"
-        return text.format(self.id, self.amount, self.currency, self.balance,
-                           self.description, self.mts)
diff --git a/bfxapi/models/margin_info.py b/bfxapi/models/margin_info.py
deleted file mode 100644
index ab376d70..00000000
--- a/bfxapi/models/margin_info.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-import datetime
-
-class MarginInfoModel:
-    """
-    Enum used to index the different values in a raw margin info array
-    """
-    TRADABLE_BALANCE = 0
-    GROSS_BALANCE = 1
-    BUY = 2
-    SELL = 3
-
-class MarginInfo:
-    """
-    SYMBOL  string
-    TRADABLE BALANCE    float
-    GROSS_BALANCE	float
-    BUY
-    SELL
-    """
-
-    def __init__(self, symbol, tradable_balance, gross_balance, buy, sell):
-        # pylint: disable=invalid-name
-        self.symbol = symbol
-        self.tradable_balance = tradable_balance
-        self.gross_balance = gross_balance
-        self.buy = buy
-        self.sell = sell
-
-    @staticmethod
-    def from_raw_margin_info(raw_margin_info):
-        """
-        Generate a MarginInfo object from a raw margin info array
-        """
-        symbol = raw_margin_info[1]
-        tradable_balance = raw_margin_info[2][MarginInfoModel.TRADABLE_BALANCE]
-        gross_balance = raw_margin_info[2][MarginInfoModel.GROSS_BALANCE]
-        buy = raw_margin_info[2][MarginInfoModel.BUY]
-        sell = raw_margin_info[2][MarginInfoModel.SELL]
-        return MarginInfo(symbol, tradable_balance, gross_balance, buy, sell)
-
-    def __str__(self):
-        return "Margin Info {} buy={} sell={} tradable_balance={} gross_balance={}" \
-               "".format(self.symbol, self.buy, self.sell, self. tradable_balance, self. gross_balance)
diff --git a/bfxapi/models/margin_info_base.py b/bfxapi/models/margin_info_base.py
deleted file mode 100644
index 806fc5ff..00000000
--- a/bfxapi/models/margin_info_base.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-import datetime
-
-class MarginInfoBaseModel:
-    """
-    Enum used to index the different values in a raw margin info array
-    """
-    USER_PL = 0
-    USER_SWAPS = 1
-    MARGIN_BALANCE = 2
-    MARGIN_NET = 3
-    MARGIN_MIN = 4
-
-class MarginInfoBase:
-    """
-    USER_PL   float
-    USER_SWAPS  float
-    MARGIN_BALANCE	 float
-    MARGIN_NET   float
-    MARGIN_MIN   float
-    """
-
-    def __init__(self, user_pl, user_swaps, margin_balance, margin_net, margin_min):
-        # pylint: disable=invalid-name
-        self.user_pl = user_pl
-        self.user_swaps = user_swaps
-        self.margin_balance = margin_balance
-        self.margin_net = margin_net
-        self.margin_min = margin_min
-
-    @staticmethod
-    def from_raw_margin_info(raw_margin_info):
-        """
-        Generate a MarginInfoBase object from a raw margin info array
-        """
-        user_pl = raw_margin_info[1][MarginInfoBaseModel.USER_PL]
-        user_swaps = raw_margin_info[1][MarginInfoBaseModel.USER_SWAPS]
-        margin_balance = raw_margin_info[1][MarginInfoBaseModel.MARGIN_BALANCE]
-        margin_net = raw_margin_info[1][MarginInfoBaseModel.MARGIN_NET]
-        margin_min = raw_margin_info[1][MarginInfoBaseModel.MARGIN_MIN]
-        return MarginInfoBase(user_pl, user_swaps, margin_balance, margin_net, margin_min)
-
-    def __str__(self):
-        return "Margin Info Base user_pl={} user_swaps={} margin_balance={} margin_net={} margin_min={}" \
-               "".format(self.user_pl, self.user_swaps, self.margin_balance, self.margin_net, self.margin_min)
diff --git a/bfxapi/models/movement.py b/bfxapi/models/movement.py
deleted file mode 100644
index d212e72a..00000000
--- a/bfxapi/models/movement.py
+++ /dev/null
@@ -1,76 +0,0 @@
-"""
-Module used to describe movement data types
-"""
-
-import time
-import datetime
-
-class MovementModel:
-    """
-    Enum used index the different values in a raw movement array
-    """
-
-    ID = 0
-    CURRENCY = 1
-    CURRENCY_NAME	= 2
-    MTS_STARTED	= 5
-    MTS_UPDATED = 6
-    STATUS = 9
-    AMOUNT = 12
-    FEES = 13
-    DESTINATION_ADDRESS	= 16
-    TRANSACTION_ID = 20
-
-class Movement:
-
-    """
-    ID	String	Movement identifier
-    CURRENCY	String	The symbol of the currency (ex. "BTC")
-    CURRENCY_NAME	String	The extended name of the currency (ex. "BITCOIN")
-    MTS_STARTED	Date	Movement started at
-    MTS_UPDATED	Date	Movement last updated at
-    STATUS	String	Current status
-    AMOUNT	String	Amount of funds moved
-    FEES	String	Tx Fees applied
-    DESTINATION_ADDRESS	String	Destination address
-    TRANSACTION_ID	String	Transaction identifier
-    """
-
-    def __init__(self, mid, currency, mts_started, mts_updated, status, amount, fees, dst_address, tx_id):
-        self.id = mid
-        self.currency = currency
-        self.mts_started = mts_started
-        self.mts_updated = mts_updated
-        self.status = status
-        self.amount = amount
-        self.fees = fees
-        self.dst_address = dst_address
-        self.tx_id = tx_id
-
-        self.date = datetime.datetime.fromtimestamp(mts_started/1000.0)
-
-
-    @staticmethod
-    def from_raw_movement(raw_movement):
-        """
-        Parse a raw movement object into a Movement object
-        @return Movement
-        """
-
-        mid = raw_movement[MovementModel.ID]
-        currency = raw_movement[MovementModel.CURRENCY]
-        mts_started = raw_movement[MovementModel.MTS_STARTED]
-        mts_updated = raw_movement[MovementModel.MTS_UPDATED]
-        status = raw_movement[MovementModel.STATUS]
-        amount = raw_movement[MovementModel.AMOUNT]
-        fees = raw_movement[MovementModel.FEES]
-        dst_address = raw_movement[MovementModel.DESTINATION_ADDRESS]
-        tx_id = raw_movement[MovementModel.TRANSACTION_ID]
-
-        return Movement(mid, currency, mts_started, mts_updated, status, amount, fees, dst_address, tx_id)
-
-    def __str__(self):
-        ''' Allow us to print the Movement object in a pretty format '''
-        text = "Movement <'{}' amount={} fees={} mts_created={} mts_updated={} status='{}' destination_address={} transaction_id={}>"
-        return text.format(self.currency, self.amount, self.fees,
-                           self.mts_started, self.mts_updated, self.status, self.dst_address, self.tx_id)
\ No newline at end of file
diff --git a/bfxapi/models/notification.py b/bfxapi/models/notification.py
deleted file mode 100644
index e4a02c61..00000000
--- a/bfxapi/models/notification.py
+++ /dev/null
@@ -1,121 +0,0 @@
-"""
-Module used to describe all of the different notification data types
-"""
-
-from .order import Order
-from .funding_offer import FundingOffer
-from .transfer import Transfer
-from .deposit_address import DepositAddress
-from .withdraw import Withdraw
-
-class NotificationModal:
-    """
-    Enum used index the different values in a raw order array
-    """
-    MTS = 0
-    TYPE = 1
-    MESSAGE_ID = 2
-    NOTIFY_INFO = 4
-    CODE = 5
-    STATUS = 6
-    TEXT = 7
-
-class NotificationError:
-    """
-    Enum used to hold the error response statuses
-    """
-    SUCCESS = "SUCCESS"
-    ERROR = "ERROR"
-    FAILURE = "FAILURE"
-
-class NotificationTypes:
-    """
-    Enum used to hold the different notification types
-    """
-    ORDER_NEW_REQ = "on-req"
-    ORDER_CANCELED_REQ = "oc-req"
-    ORDER_UPDATED_REQ = "ou-req"
-    FUNDING_OFFER_NEW = "fon-req"
-    FUNDING_OFFER_CANCEL = "foc-req"
-    ACCOUNT_TRANSFER = "acc_tf"
-    ACCOUNT_DEPOSIT = "acc_dep"
-    ACCOUNT_WITHDRAW_REQ = "acc_wd-req"
-    # uca ?
-    # pm-req ?
-
-
-class Notification:
-    """
-    MTS	int	Millisecond Time Stamp of the update
-    TYPE string	Purpose of notification ('on-req', 'oc-req', 'uca', 'fon-req', 'foc-req')
-    MESSAGE_ID	int	unique ID of the message
-    NOTIFY_INFO	array/object	A message containing information regarding the notification
-    CODE null or integer	Work in progress
-    STATUS string	Status of the notification; it may vary over time (SUCCESS, ERROR, FAILURE, ...)
-    TEXT string	Text of the notification
-    """
-
-    def __init__(self, mts, notify_type, message_id, notify_info, code, status, text):
-        self.mts = mts
-        self.notify_type = notify_type
-        self.message_id = message_id
-        self.notify_info = notify_info
-        self.code = code
-        self.status = status
-        self.text = text
-
-    def is_success(self):
-        """
-        Check if the notification status was a success.
-
-        @return bool: True if is success else False
-        """
-        if self.status == NotificationError.SUCCESS:
-            return True
-        return False
-
-    @staticmethod
-    def from_raw_notification(raw_notification):
-        """
-        Parse a raw notification object into an Order object
-
-        @return Notification
-        """
-        mts = raw_notification[NotificationModal.MTS]
-        notify_type = raw_notification[NotificationModal.TYPE]
-        message_id = raw_notification[NotificationModal.MESSAGE_ID]
-        notify_info = raw_notification[NotificationModal.NOTIFY_INFO]
-        code = raw_notification[NotificationModal.CODE]
-        status = raw_notification[NotificationModal.STATUS]
-        text = raw_notification[NotificationModal.TEXT]
-
-        basic = Notification(mts, notify_type, message_id, notify_info, code,
-                             status, text)
-        # if failure notification then just return as is
-        if not basic.is_success():
-            return basic
-        # parse additional notification data
-        if basic.notify_type == NotificationTypes.ORDER_NEW_REQ:
-            basic.notify_info = Order.from_raw_order_snapshot(basic.notify_info)
-        elif basic.notify_type == NotificationTypes.ORDER_CANCELED_REQ:
-            basic.notify_info = Order.from_raw_order(basic.notify_info)
-        elif basic.notify_type == NotificationTypes.ORDER_UPDATED_REQ:
-            basic.notify_info = Order.from_raw_order(basic.notify_info)
-        elif basic.notify_type == NotificationTypes.FUNDING_OFFER_NEW:
-            basic.notify_info = FundingOffer.from_raw_offer(basic.notify_info)
-        elif basic.notify_type == NotificationTypes.FUNDING_OFFER_CANCEL:
-            basic.notify_info = FundingOffer.from_raw_offer(basic.notify_info)
-        elif basic.notify_type == NotificationTypes.ACCOUNT_TRANSFER:
-            basic.notify_info = Transfer.from_raw_transfer(basic.notify_info)
-        elif basic.notify_type == NotificationTypes.ACCOUNT_DEPOSIT:
-            basic.notify_info = DepositAddress.from_raw_deposit_address(basic.notify_info)
-        elif basic.notify_type == NotificationTypes.ACCOUNT_WITHDRAW_REQ:
-            basic.notify_info = Withdraw.from_raw_withdraw(basic.notify_info)
-        return basic
-
-    def __str__(self):
-        """
-        Allow us to print the Notification object in a pretty format
-        """
-        text = "Notification <'{}' ({}) - {} notify_info={}>"
-        return text.format(self.notify_type, self.status, self.text, self.notify_info)
diff --git a/bfxapi/models/order.py b/bfxapi/models/order.py
deleted file mode 100644
index 1a8348cc..00000000
--- a/bfxapi/models/order.py
+++ /dev/null
@@ -1,238 +0,0 @@
-"""
-Module used to describe all of the different order data types
-"""
-
-import time
-import datetime
-
-class OrderType:
-    """
-    Enum used to describe all of the different order types available for use
-    """
-    MARKET = 'MARKET'
-    LIMIT = 'LIMIT'
-    STOP = 'STOP'
-    STOP_LIMIT = 'STOP LIMIT'
-    TRAILING_STOP = 'TRAILING STOP'
-    FILL_OR_KILL = 'FOK'
-    EXCHANGE_MARKET = 'EXCHANGE MARKET'
-    EXCHANGE_LIMIT = 'EXCHANGE LIMIT'
-    EXCHANGE_STOP = 'EXCHANGE STOP'
-    EXCHANGE_STOP_LIMIT = 'EXCHANGE STOP LIMIT'
-    EXCHANGE_TRAILING_STOP = 'EXCHANGE TRAILING STOP'
-    EXCHANGE_FILL_OR_KILL = 'EXCHANGE FOK'
-
-
-LIMIT_ORDERS = [OrderType.LIMIT, OrderType.STOP_LIMIT, OrderType.EXCHANGE_LIMIT,
-                OrderType.EXCHANGE_STOP_LIMIT, OrderType.FILL_OR_KILL,
-                OrderType.EXCHANGE_FILL_OR_KILL]
-
-
-class OrderSide:
-    """
-    Enum used to describe the different directions of an order
-    """
-    BUY = 'buy'
-    SELL = 'sell'
-
-
-class OrderClosedModel:
-    """
-    Enum used to index the different values in a raw order array
-    """
-    ID = 0
-    GID = 1
-    CID = 2
-    SYMBOL = 3
-    MTS_CREATE = 4
-    MTS_UPDATE = 5
-    AMOUNT = 6
-    AMOUNT_ORIG = 7
-    TYPE = 8
-    TYPE_PREV = 9
-    FLAGS = 12
-    STATUS = 13
-    PRICE = 16
-    PRICE_AVG = 17
-    PRICE_TRAILING = 18
-    PRICE_AUX_LIMIT = 19
-    NOTIFY = 23
-    PLACE_ID = 25
-    META = 31
-
-
-class OrderFlags:
-    """
-    Enum used to explain the different values that can be passed in
-    as flags
-    """
-    HIDDEN = 64
-    CLOSE = 512
-    REDUCE_ONLY = 1024
-    POST_ONLY = 4096
-    OCO = 16384
-
-
-def now_in_mills():
-    """
-    Gets the current time in milliseconds
-    """
-    return int(round(time.time() * 1000))
-
-
-class Order:
-    """
-    ID	int64	Order ID
-    GID	int	Group ID
-    CID	int	Client Order ID
-    SYMBOL	string	Pair (tBTCUSD, ...)
-    MTS_CREATE	int	Millisecond timestamp of creation
-    MTS_UPDATE	int	Millisecond timestamp of update
-    AMOUNT	float	Positive means buy, negative means sell.
-    AMOUNT_ORIG	float	Original amount
-    TYPE	string	The type of the order: LIMIT, MARKET, STOP, TRAILING STOP,
-      EXCHANGE MARKET, EXCHANGE LIMIT, EXCHANGE STOP, EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK.
-    TYPE_PREV	string	Previous order type
-    FLAGS	int	Upcoming Params Object (stay tuned)
-    ORDER_STATUS	string	Order Status: ACTIVE, EXECUTED, PARTIALLY FILLED, CANCELED
-    PRICE	float	Price
-    PRICE_AVG	float	Average price
-    PRICE_TRAILING	float	The trailing price
-    PRICE_AUX_LIMIT	float	Auxiliary Limit price (for STOP LIMIT)
-    HIDDEN	int	1 if Hidden, 0 if not hidden
-    PLACED_ID	int	If another order caused this order to be placed (OCO) this will be that other
-    order's ID
-    """
-
-    Type = OrderType()
-    Side = OrderSide()
-    Flags = OrderFlags()
-
-    def __init__(self, oid, gid, cid, symbol, mts_create, mts_update, amount,
-                 amount_orig, o_type, typePrev, flags, status, price, price_avg,
-                 price_trailing, price_aux_limit, notfiy, place_id, meta):
-        self.id = oid # pylint: disable=invalid-name
-        self.gid = gid
-        self.cid = cid
-        self.symbol = symbol
-        self.mts_create = mts_create
-        self.mts_update = mts_update
-        self.amount = amount
-        self.amount_orig = amount_orig
-        if self.amount_orig > 0:
-            self.amount_filled = amount_orig - amount
-        else:
-            self.amount_filled = -(abs(amount_orig) - abs(amount))
-        self.type = o_type
-        self.type_prev = typePrev
-        self.flags = flags
-        self.status = status
-        self.price = price
-        self.price_avg = price_avg
-        self.price_trailing = price_trailing
-        self.price_aux_limit = price_aux_limit
-        self.notfiy = notfiy
-        self.place_id = place_id
-        self.tag = ""
-        self.fee = 0
-        self.is_pending_bool = True
-        self.is_confirmed_bool = False
-        self.is_open_bool = False
-        self.meta = meta or {}
-
-        self.date = datetime.datetime.fromtimestamp(mts_create/1000.0)
-        # if cancelled then priceAvg wont exist
-        if price_avg:
-            # check if order is taker or maker
-            if self.type in LIMIT_ORDERS:
-                self.fee = (price_avg * abs(self.amount_filled)) * 0.001
-            else:
-                self.fee = (price_avg * abs(self.amount_filled)) * 0.002
-
-    @staticmethod
-    def from_raw_order(raw_order):
-        """
-        Parse a raw order object into an Order object
-
-        @return Order
-        """
-        oid = raw_order[OrderClosedModel.ID]
-        gid = raw_order[OrderClosedModel.GID]
-        cid = raw_order[OrderClosedModel.CID]
-        symbol = raw_order[OrderClosedModel.SYMBOL]
-        mts_create = raw_order[OrderClosedModel.MTS_CREATE]
-        mts_update = raw_order[OrderClosedModel.MTS_UPDATE]
-        amount = raw_order[OrderClosedModel.AMOUNT]
-        amount_orig = raw_order[OrderClosedModel.AMOUNT_ORIG]
-        o_type = raw_order[OrderClosedModel.TYPE]
-        type_prev = raw_order[OrderClosedModel.TYPE_PREV]
-        flags = raw_order[OrderClosedModel.FLAGS]
-        status = raw_order[OrderClosedModel.STATUS]
-        price = raw_order[OrderClosedModel.PRICE]
-        price_avg = raw_order[OrderClosedModel.PRICE_AVG]
-        price_trailing = raw_order[OrderClosedModel.PRICE_TRAILING]
-        price_aux_limit = raw_order[OrderClosedModel.PRICE_AUX_LIMIT]
-        notfiy = raw_order[OrderClosedModel.NOTIFY]
-        place_id = raw_order[OrderClosedModel.PLACE_ID]
-        meta = raw_order[OrderClosedModel.META] or {}
-
-        return Order(oid, gid, cid, symbol, mts_create, mts_update, amount,
-                     amount_orig, o_type, type_prev, flags, status, price, price_avg,
-                     price_trailing, price_aux_limit, notfiy, place_id, meta)
-
-    @staticmethod
-    def from_raw_order_snapshot(raw_order_snapshot):
-        """
-        Parse a raw order snapshot array into an array of order objects
-
-        @return Orders: array of order objects
-        """
-        parsed_orders = []
-        for raw_order in raw_order_snapshot:
-            parsed_orders += [Order.from_raw_order(raw_order)]
-        return parsed_orders
-
-    def set_confirmed(self):
-        """
-        Set the state of the order to be confirmed
-        """
-        self.is_pending_bool = False
-        self.is_confirmed_bool = True
-
-    def set_open_state(self, is_open):
-        """
-        Set the is_open state of the order
-        """
-        self.is_open_bool = is_open
-
-    def is_open(self):
-        """
-        Check if the order is still open
-
-        @return bool: True if order open else False
-        """
-        return self.is_open_bool
-
-    def is_pending(self):
-        """
-        Check if the state of the order is still pending
-
-        @return bool: True if is pending else False
-        """
-        return self.is_pending_bool
-
-    def is_confirmed(self):
-        """
-        Check if the order has been confirmed by the bitfinex api
-
-        @return bool: True if has been confirmed else False
-        """
-        return self.is_confirmed_bool
-
-    def __str__(self):
-        """
-        Allow us to print the Order object in a pretty format
-        """
-        text = "Order <'{}' amount_orig={} amount_filled={} mts_create={} status='{}' id={}>"
-        return text.format(self.symbol, self.amount_orig, self.amount_filled,
-                           self.mts_create, self.status, self.id)
diff --git a/bfxapi/models/order_book.py b/bfxapi/models/order_book.py
deleted file mode 100644
index f00cfbbb..00000000
--- a/bfxapi/models/order_book.py
+++ /dev/null
@@ -1,124 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-import zlib
-import json
-
-class OrderBook:
-    """
-    Object used to store the state of the orderbook. This can then be used
-    in one of two ways. To get the checksum of the book or so get the bids/asks
-    of the book
-    """
-
-    def __init__(self):
-        self.asks = []
-        self.bids = []
-
-    def get_bids(self):
-        """
-        Get all of the bids from the orderbook
-
-        @return bids Array
-        """
-        return self.bids
-
-    def get_asks(self):
-        """
-        Get all of the asks from the orderbook
-
-        @return asks Array
-        """
-        return self.asks
-
-    def update_from_snapshot(self, data, orig_raw_msg):
-        """
-        Update the orderbook with a raw orderbook snapshot
-        """
-        # we need to keep the original string values that are sent to use
-        # this avoids any problems with floats
-        orig_raw = json.loads(orig_raw_msg, parse_float=str, parse_int=str)[1]
-        zip_data = []
-        # zip both the float values and string values together
-        for index, order in enumerate(data):
-            zip_data += [(order, orig_raw[index])]
-        ## build our bids and asks
-        for order in zip_data:
-            if len(order[0]) == 4:
-                if order[0][3] < 0:
-                    self.bids += [order]
-                else:
-                    self.asks += [order]
-            else:
-                if order[0][2] < 0:
-                    self.asks += [order]
-                else:
-                    self.bids += [order]
-
-    def update_with(self, order, orig_raw_msg):
-        """
-        Update the orderbook with a single update
-        """
-        # keep orginal string vlues to avoid checksum float errors
-        orig_raw = json.loads(orig_raw_msg, parse_float=str, parse_int=str)[1]
-        zip_order = (order, orig_raw)
-        if len(order) == 4:
-            amount = order[3]
-            count = order[2]
-            side = self.bids if amount < 0 else self.asks
-        else:
-            amount = order[2]
-            side = self.asks if amount < 0 else self.bids
-            count = order[1]
-        price = order[0]
-
-        # if first item in ordebook
-        if len(side) == 0:
-            side += [zip_order]
-            return
-
-        # match price level but use the float parsed object
-        for index, s_order in enumerate(side):
-            s_price = s_order[0][0]
-            if s_price == price:
-                if count == 0:
-                    del side[index]
-                    return
-                # remove but add as new below
-                del side[index]
-
-        # if ob is initialised w/o all price levels
-        if count == 0:
-            return
-
-        # add to book and sort lowest to highest
-        side += [zip_order]
-        side.sort(key=lambda x: x[0][0], reverse=not amount < 0)
-        return
-
-    def checksum(self):
-        """
-        Generate a CRC32 checksum of the orderbook
-        """
-        data = []
-        # take set of top 25 bids/asks
-        for index in range(0, 25):
-            if index < len(self.bids):
-                # use the string parsed array
-                bid = self.bids[index][1]
-                price = bid[0]
-                amount = bid[3] if len(bid) == 4 else bid[2]
-                data += [price]
-                data += [amount]
-            if index < len(self.asks):
-                # use the string parsed array
-                ask = self.asks[index][1]
-                price = ask[0]
-                amount = ask[3] if len(ask) == 4 else ask[2]
-                data += [price]
-                data += [amount]
-        checksum_str = ':'.join(data)
-        # calculate checksum and force signed integer
-        checksum = zlib.crc32(checksum_str.encode('utf8')) & 0xffffffff
-        return checksum
diff --git a/bfxapi/models/position.py b/bfxapi/models/position.py
deleted file mode 100644
index 91ca2ae4..00000000
--- a/bfxapi/models/position.py
+++ /dev/null
@@ -1,108 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-class PositionModel:
-    """
-    Enum used to index the different values in a raw position array
-    """
-    SYMBOL = 0
-    STATUS = 1
-    AMOUNT = 2
-    BASE_PRICE = 3
-    MARGIN_FUNDING = 4
-    MARGIN_FUNDING_TYPE = 5
-    PL = 6
-    PL_PERC = 7
-    PRICE_LIQ = 8
-    LEVERAGE = 9
-    # _PLACEHOLDER,
-    POSITION_ID = 11
-    MTS_CREATE = 12
-    MTS_UPDATE = 13
-    # _PLACEHOLDER
-    TYPE = 15
-    # _PLACEHOLDER,
-    COLLATERAL = 17
-    COLLATERAL_MIN = 18
-    META = 19
-
-class Position:
-    """
-    SYMBOL	        string	Pair (tBTCUSD, …).
-    STATUS	        string	Status (ACTIVE, CLOSED).
-    ±AMOUNT	        float	Size of the position. A positive value indicates a
-                    long position; a negative value indicates a short position.
-    BASE_PRICE	    float	Base price of the position. (Average traded price
-                    of the previous orders of the position)
-    MARGIN_FUNDING	float	The amount of funding being used for this position.
-    MARGIN_FUNDING_TYPE	int	0 for daily, 1 for term.
-    PL	            float	Profit & Loss
-    PL_PERC	        float	Profit & Loss Percentage
-    PRICE_LIQ	    float	Liquidation price
-    LEVERAGE	    float	Leverage used for the position
-    POSITION_ID	    int64	Position ID
-    MTS_CREATE	    int	Millisecond timestamp of creation
-    MTS_UPDATE	    int	Millisecond timestamp of update
-    TYPE	        int	Identifies the type of position, 0 = Margin position,
-                    1 = Derivatives position
-    COLLATERAL	    float	The amount of collateral applied to the open position
-    COLLATERAL_MIN	float	The minimum amount of collateral required for the position
-    META	        json string	Additional meta information about the position
-    """
-
-    def __init__(self, symbol, status, amount, b_price, m_funding, m_funding_type,
-                 profit_loss, profit_loss_perc, l_price, lev, pid, mts_create, mts_update,
-                 p_type, collateral, collateral_min, meta):
-        self.symbol = symbol
-        self.status = status
-        self.amount = amount
-        self.base_price = b_price
-        self.margin_funding = m_funding
-        self.margin_funding_type = m_funding_type
-        self.profit_loss = profit_loss
-        self.profit_loss_percentage = profit_loss_perc
-        self.liquidation_price = l_price
-        self.leverage = lev
-        self.id = pid
-        self.mts_create = mts_create
-        self.mts_update = mts_update
-        self.type = p_type
-        self.collateral = collateral
-        self.collateral_min = collateral_min
-        self.meta = meta
-
-    @staticmethod
-    def from_raw_rest_position(raw_position):
-        """
-        Generate a Position object from a raw position array
-
-        @return Position
-        """
-        sym = raw_position[PositionModel.SYMBOL]
-        status = raw_position[PositionModel.STATUS]
-        amnt = raw_position[PositionModel.AMOUNT]
-        b_price = raw_position[PositionModel.BASE_PRICE]
-        m_fund = raw_position[PositionModel.MARGIN_FUNDING]
-        m_fund_t = raw_position[PositionModel.MARGIN_FUNDING_TYPE]
-        pl = raw_position[PositionModel.PL]
-        pl_prc = raw_position[PositionModel.PL_PERC]
-        l_price = raw_position[PositionModel.PRICE_LIQ]
-        lev = raw_position[PositionModel.LEVERAGE]
-        pid = raw_position[PositionModel.POSITION_ID]
-        mtsc = raw_position[PositionModel.MTS_CREATE]
-        mtsu = raw_position[PositionModel.MTS_UPDATE]
-        ptype = raw_position[PositionModel.TYPE]
-        coll = raw_position[PositionModel.COLLATERAL]
-        coll_min = raw_position[PositionModel.COLLATERAL_MIN]
-        meta = raw_position[PositionModel.META]
-
-        return Position(sym, status, amnt, b_price, m_fund, m_fund_t, pl, pl_prc, l_price,
-                        lev, pid, mtsc, mtsu, ptype, coll, coll_min, meta)
-
-    def __str__(self):
-        """
-        Allow us to print the Trade object in a pretty format
-        """
-        text = "Position '{}' {} x {} <status='{}' pl={}>"
-        return text.format(self.symbol, self.base_price, self.amount,
-                           self.status, self.profit_loss)
diff --git a/bfxapi/models/subscription.py b/bfxapi/models/subscription.py
deleted file mode 100644
index 55dfebab..00000000
--- a/bfxapi/models/subscription.py
+++ /dev/null
@@ -1,88 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-import time
-import json
-from random import randint
-
-def generate_sub_id():
-    """
-    Generates a unique id in the form of 12345566-12334556
-    """
-    prefix = str(int(round(time.time() * 1000)))
-    suffix = str(randint(0, 9999999))
-    return "{}-{}".format(prefix, suffix)
-
-class Subscription:
-    """
-    Object used to represent an individual subscription to the websocket.
-    This class also exposes certain functions which helps to manage the subscription
-    such as unsubscribe and subscribe.
-    """
-
-    def __init__(self, socket, channel_name, symbol, key=None, timeframe=None, **kwargs):
-        self.socket = socket
-        self.channel_name = channel_name
-        self.symbol = symbol
-        self.timeframe = timeframe
-        self.is_subscribed_bool = False
-        self.key = key
-        self.chan_id = None
-        if timeframe:
-            self.key = 'trade:{}:{}'.format(self.timeframe, self.symbol)
-        self.sub_id = generate_sub_id()
-        self.send_payload = self._generate_payload(**kwargs)
-
-    def get_key(self):
-        """
-        Generates a unique key string for the subscription
-        """
-        return "{}_{}".format(self.channel_name, self.key or self.symbol)
-
-    def confirm_subscription(self, chan_id):
-        """
-        Update the subscription to confirmed state
-        """
-        self.is_subscribed_bool = True
-        self.chan_id = chan_id
-
-    async def unsubscribe(self):
-        """
-        Send an un-subscription request to the bitfinex socket
-        """
-        if not self.is_subscribed():
-            raise Exception("Subscription is not subscribed to websocket")
-        payload = {'event': 'unsubscribe', 'chanId': self.chan_id}
-        await self.socket.send(json.dumps(payload))
-
-    async def subscribe(self):
-        """
-        Send a subscription request to the bitfinex socket
-        """
-        await self.socket.send(json.dumps(self._get_send_payload()))
-
-    def confirm_unsubscribe(self):
-        """
-        Update the subscription to unsubscribed state
-        """
-        self.is_subscribed_bool = False
-
-    def is_subscribed(self):
-        """
-        Check if the subscription is currently subscribed
-
-        @return bool: True if subscribed else False
-        """
-        return self.is_subscribed_bool
-
-    def _generate_payload(self, **kwargs):
-        payload = {'event': 'subscribe',
-                   'channel': self.channel_name, 'symbol': self.symbol}
-        if self.timeframe or self.key:
-            payload['key'] = self.key
-        payload.update(**kwargs)
-        return payload
-
-    def _get_send_payload(self):
-        return self.send_payload
diff --git a/bfxapi/models/ticker.py b/bfxapi/models/ticker.py
deleted file mode 100644
index f88419f7..00000000
--- a/bfxapi/models/ticker.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-class TickerModel:
-    """
-    Enum used to index the different values in a raw ticker array
-    """
-    BID = 0
-    BID_SIZE = 1
-    ASK = 2
-    ASK_SIZE = 3
-    DAILY_CHANGE = 4
-    DAILY_CHANGE_PERCENT = 5
-    LAST_PRICE = 6
-    VOLUME = 7
-    HIGH = 8
-    LOW = 9
-
-class Ticker:
-    """
-    BID	float	    Price of last highest bid
-    BID_SIZE	    float	Sum of the 25 highest bid sizes
-    ASK	float	    Price of last lowest ask
-    ASK_SIZE	    float	Sum of the 25 lowest ask sizes
-    DAILY_CHANGE	float	Amount that the last price has changed since yesterday
-    DAILY_CHANGE_PERCENT	float	Relative price change since yesterday (*100 for percentage change)
-    LAST_PRICE	    float	Price of the last trade
-    VOLUME	        float	Daily volume
-    HIGH	        float	Daily high
-    LOW	            float	Daily low
-    """
-
-    def __init__(self, pair, bid, bid_size, ask, ask_size, daily_change, daily_change_rel,
-                 last_price, volume, high, low):
-        self.pair = pair
-        self.bid = bid
-        self.bid_size = bid_size
-        self.ask = ask
-        self.ask_size = ask_size
-        self.daily_change = daily_change
-        self.daily_change_rel = daily_change_rel
-        self.last_price = last_price
-        self.volume = volume
-        self.high = high
-        self.low = low
-
-    @staticmethod
-    def from_raw_ticker(raw_ticker, pair):
-        """
-        Generate a Ticker object from a raw ticker array
-        """
-        # [72128,[6914.5,28.123061460000002,6914.6,22.472037289999996,175.8,0.0261,6915.7,
-        # 6167.26141685,6964.2,6710.8]]
-
-        return Ticker(
-            pair,
-            raw_ticker[TickerModel.BID],
-            raw_ticker[TickerModel.BID_SIZE],
-            raw_ticker[TickerModel.ASK],
-            raw_ticker[TickerModel.ASK_SIZE],
-            raw_ticker[TickerModel.DAILY_CHANGE],
-            raw_ticker[TickerModel.DAILY_CHANGE_PERCENT],
-            raw_ticker[TickerModel.LAST_PRICE],
-            raw_ticker[TickerModel.VOLUME],
-            raw_ticker[TickerModel.HIGH],
-            raw_ticker[TickerModel.LOW],
-        )
-
-    def __str__(self):
-        return "Ticker '{}' <last='{}' volume={}>".format(
-            self.pair, self.last_price, self.volume)
diff --git a/bfxapi/models/trade.py b/bfxapi/models/trade.py
deleted file mode 100644
index 79d22377..00000000
--- a/bfxapi/models/trade.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-import datetime
-
-class TradeModel:
-    """
-    Enum used to index the different values in a raw trade array
-    """
-    ID = 0
-    PAIR = 1
-    MTS_CREATE = 2
-    ORDER_ID = 3
-    EXEC_AMOUNT = 4
-    EXEC_PRICE = 5
-    ORDER_TYPE = 6
-    ORDER_PRICE = 7
-    MAKER = 8
-    FEE = 9
-    FEE_CURRENCY = 10
-
-class Trade:
-    """
-    ID	integer	Trade database id
-    PAIR	string	Pair (BTCUSD, ...)
-    MTS_CREATE	integer	Execution timestamp
-    ORDER_ID	integer	Order id
-    EXEC_AMOUNT	float	Positive means buy, negative means sell
-    EXEC_PRICE	float	Execution price
-    ORDER_TYPE	string	Order type
-    ORDER_PRICE	float	Order price
-    MAKER	int	1 if true, 0 if false
-    FEE	float	Fee
-    FEE_CURRENCY	string	Fee currency
-    """
-
-    SHORT = 'SHORT'
-    LONG = 'LONG'
-
-    def __init__(self, tid, pair, mts_create, order_id, amount, price, order_type,
-                 order_price, maker, fee, fee_currency):
-        # pylint: disable=invalid-name
-        self.id = tid
-        self.pair = pair
-        self.mts_create = mts_create
-        self.date = datetime.datetime.fromtimestamp(mts_create/1000.0)
-        self.order_id = order_id
-        self.amount = amount
-        self.direction = Trade.SHORT if amount < 0 else Trade.LONG
-        self.price = price
-        self.order_type = order_type
-        self.order_price = order_price
-        self.maker = maker
-        self.fee = fee
-        self.fee_currency = fee_currency
-
-    @staticmethod
-    def from_raw_rest_trade(raw_trade):
-        """
-        Generate a Trade object from a raw trade array
-        """
-        # [24224048, 'tBTCUSD', 1542800024000, 1151353484, 0.09399997, 19963, None, None,
-        # -1, -0.000188, 'BTC']
-        tid = raw_trade[TradeModel.ID]
-        pair = raw_trade[TradeModel.PAIR]
-        mtsc = raw_trade[TradeModel.MTS_CREATE]
-        oid = raw_trade[TradeModel.ORDER_ID]
-        amnt = raw_trade[TradeModel.EXEC_AMOUNT]
-        price = raw_trade[TradeModel.EXEC_PRICE]
-        otype = raw_trade[TradeModel.ORDER_TYPE]
-        oprice = raw_trade[TradeModel.ORDER_PRICE]
-        maker = raw_trade[TradeModel.MAKER]
-        fee = raw_trade[TradeModel.FEE]
-        feeccy = raw_trade[TradeModel.FEE_CURRENCY]
-        return Trade(tid, pair, mtsc, oid, amnt, price, otype, oprice, maker,
-                     fee, feeccy)
-
-    def __str__(self):
-        return "Trade '{}' x {} @ {} <direction='{}' fee={}>".format(
-            self.pair, self.amount, self.price, self.direction, self.fee)
diff --git a/bfxapi/models/transfer.py b/bfxapi/models/transfer.py
deleted file mode 100644
index 544250fd..00000000
--- a/bfxapi/models/transfer.py
+++ /dev/null
@@ -1,55 +0,0 @@
-"""
-Module used to describe a transfer object
-"""
-
-class TransferModel:
-    """
-    Enum used to index the location of each value in a raw array
-    """
-    MTS = 0
-    W_FROM = 1
-    W_TO = 2
-    C_FROM = 4
-    C_TO = 5
-    AMOUNT = 7
-
-class Transfer:
-    """
-    MTS	int	Millisecond Time Stamp of the update
-    WALLET_FROM string	Wallet name (exchange, margin, funding)
-    WALLET_TO string	Wallet name (exchange, margin, funding)
-    CURRENCY_FROM 	string Currency (BTC, etc)
-    CURRENCY_TO	 string Currency (BTC, etc)
-    AMOUNT string  Amount of funds to transfer
-    """
-
-    def __init__(self, mts, wallet_from, wallet_to, currency_from, currency_to, amount):
-        self.mts = mts
-        self.wallet_from = wallet_from
-        self.wallet_to = wallet_to
-        self.currency_from = currency_from
-        self.currency_to = currency_to
-        self.amount = amount
-
-    @staticmethod
-    def from_raw_transfer(raw_transfer):
-        """
-        Parse a raw transfer object into a Transfer object
-
-        @return Transfer
-        """
-        mts = raw_transfer[TransferModel.MTS]
-        wallet_from = raw_transfer[TransferModel.W_FROM]
-        wallet_to = raw_transfer[TransferModel.W_TO]
-        currency_from = raw_transfer[TransferModel.C_FROM]
-        currency_to = raw_transfer[TransferModel.C_TO]
-        amount = raw_transfer[TransferModel.AMOUNT]
-        return Transfer(mts, wallet_from, wallet_to, currency_from, currency_to, amount)
-
-    def __str__(self):
-        """
-        Allow us to print the Transfer object in a pretty format
-        """
-        text = "Transfer <{} from {} ({}) to {} ({}) mts={}>"
-        return text.format(self.amount, self.wallet_from, self.currency_from,
-                           self.wallet_to, self.currency_to, self.mts)
diff --git a/bfxapi/models/wallet.py b/bfxapi/models/wallet.py
deleted file mode 100644
index 7fa292aa..00000000
--- a/bfxapi/models/wallet.py
+++ /dev/null
@@ -1,34 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-
-class Wallet:
-    """
-    Stores data relevant to a users wallet such as balance and
-    currency
-    """
-
-    def __init__(self, wType, currency, balance, unsettled_interest, balance_available):
-        self.type = wType
-        self.currency = currency
-        self.balance = balance
-        self.balance_available = balance_available
-        self.unsettled_interest = unsettled_interest
-        self.key = "{}_{}".format(wType, currency)
-
-    def set_balance(self, data):
-        """
-        Set the balance of the wallet
-        """
-        self.balance = data
-
-    def set_unsettled_interest(self, data):
-        """
-        Set the unsettled interest of the wallet
-        """
-        self.unsettled_interest = data
-
-    def __str__(self):
-        return "Wallet <'{}_{}' balance='{}' balance_available='{}' unsettled='{}'>".format(
-            self.type, self.currency, self.balance, self.balance_available, self.unsettled_interest)
diff --git a/bfxapi/models/withdraw.py b/bfxapi/models/withdraw.py
deleted file mode 100644
index 0558527e..00000000
--- a/bfxapi/models/withdraw.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""
-Module used to describe a withdraw object
-"""
-
-class WithdrawModel:
-    """
-    Enum used to index the location of each value in a raw array
-    """
-    ID = 0
-    METHOD = 2
-    WALLET = 4
-    AMOUNT = 5
-    FEE = 8
-
-class Withdraw:
-    """
-    [13063236, None, 'tetheruse', None, 'exchange', 5, None, None, 0.00135]
-
-    MTS	int	Millisecond Time Stamp of the update
-    WALLET_FROM string	Wallet name (exchange, margin, funding)
-    WALLET_TO string	Wallet name (exchange, margin, funding)
-    CURRENCY_FROM 	string Currency (BTC, etc)
-    CURRENCY_TO	 string Currency (BTC, etc)
-    AMOUNT string  Amount of funds to transfer
-    """
-
-    def __init__(self, w_id, method, wallet, amount, fee=0):
-        self.id = w_id
-        self.method = method
-        self.wallet = wallet
-        self.amount = amount
-        self.fee = fee
-
-    @staticmethod
-    def from_raw_withdraw(raw_withdraw):
-        """
-        Parse a raw withdraw object into a Withdraw object
-
-        @return Withdraw
-        """
-        w_id = raw_withdraw[WithdrawModel.ID]
-        method = raw_withdraw[WithdrawModel.METHOD]
-        wallet = raw_withdraw[WithdrawModel.WALLET]
-        amount = raw_withdraw[WithdrawModel.AMOUNT]
-        fee = raw_withdraw[WithdrawModel.FEE]
-        return Withdraw(w_id, method, wallet, amount, fee)
-
-    def __str__(self):
-        """
-        Allow us to print the Withdraw object in a pretty format
-        """
-        text = "Withdraw <id={} from {} ({}) amount={} fee={}>"
-        return text.format(self.id, self.wallet, self.method, self.amount,
-                           self.fee)
diff --git a/bfxapi/notification.py b/bfxapi/notification.py
new file mode 100644
index 00000000..ae022593
--- /dev/null
+++ b/bfxapi/notification.py
@@ -0,0 +1,38 @@
+from typing import List, Optional, Any, Generic, TypeVar, cast
+from dataclasses import dataclass
+from .labeler import _Type, _Serializer
+
+T = TypeVar("T")
+
+@dataclass
+class Notification(_Type, Generic[T]):
+    mts: int
+    type: str
+    message_id: Optional[int]
+    data: T
+    code: Optional[int]
+    status: str
+    text: str
+
+class _Notification(_Serializer, Generic[T]):
+    __LABELS = [ "mts", "type", "message_id", "_PLACEHOLDER", "data", "code", "status", "text" ]
+
+    def __init__(self, serializer: Optional[_Serializer] = None, is_iterable: bool = False):
+        super().__init__("Notification", Notification, _Notification.__LABELS, ignore = [ "_PLACEHOLDER" ])
+
+        self.serializer, self.is_iterable = serializer, is_iterable
+
+    def parse(self, *values: Any) -> Notification[T]:
+        notification = cast(Notification[T], Notification(**dict(self._serialize(*values))))
+
+        if isinstance(self.serializer, _Serializer):
+            data = cast(List[Any], notification.data)
+
+            if not self.is_iterable:
+                if len(data) == 1 and isinstance(data[0], list):
+                    data = data[0]
+
+                notification.data = self.serializer.parse(*data)
+            else: notification.data = cast(T, [ self.serializer.parse(*sub_data) for sub_data in data ])
+
+        return notification
diff --git a/bfxapi/rest/__init__.py b/bfxapi/rest/__init__.py
index dcba731e..e18526e6 100644
--- a/bfxapi/rest/__init__.py
+++ b/bfxapi/rest/__init__.py
@@ -1,1 +1,2 @@
-NAME = 'rest'
+from .endpoints import BfxRestInterface, RestPublicEndpoints, RestAuthenticatedEndpoints, \
+                    RestMerchantEndpoints
diff --git a/bfxapi/rest/bfx_rest.py b/bfxapi/rest/bfx_rest.py
deleted file mode 100644
index fabd4f9e..00000000
--- a/bfxapi/rest/bfx_rest.py
+++ /dev/null
@@ -1,1412 +0,0 @@
-"""
-This module contains the BFX rest client data types
-"""
-
-import asyncio
-import aiohttp
-import time
-import json
-import datetime
-
-from ..utils.custom_logger import CustomLogger
-from ..utils.auth import generate_auth_headers, calculate_order_flags, gen_unique_cid
-from ..models import Wallet, Order, Position, Trade, FundingLoan, FundingOffer, FundingTrade, MarginInfoBase, MarginInfo
-from ..models import FundingCredit, Notification, Ledger, Movement
-
-
-class BfxRest:
-    """
-    BFX rest interface contains functions which are used to interact with both the public
-    and private Bitfinex http api's.
-    To use the private api you have to set the API_KEY and API_SECRET variables to your
-    api key.
-    """
-
-    def __init__(self, API_KEY, API_SECRET, host='https://api-pub.bitfinex.com/v2', loop=None,
-                 logLevel='INFO', parse_float=float, *args, **kwargs):
-        self.loop = loop or asyncio.get_event_loop()
-        self.API_KEY = API_KEY
-        self.API_SECRET = API_SECRET
-        self.host = host
-        # this value can also be set to bfxapi.decimal for much higher precision
-        self.parse_float = parse_float
-        self.logger = CustomLogger('BfxRest', logLevel=logLevel)
-
-    async def fetch(self, endpoint, params=""):
-        """
-        Send a GET request to the bitfinex api
-
-        @return reponse
-        """
-        url = '{}/{}{}'.format(self.host, endpoint, params)
-        async with aiohttp.ClientSession() as session:
-            async with session.get(url) as resp:
-                text = await resp.text()
-                if resp.status != 200:
-                    raise Exception('GET {} failed with status {} - {}'
-                                    .format(url, resp.status, text))
-                parsed = json.loads(text, parse_float=self.parse_float)
-                return parsed
-
-    async def post(self, endpoint, data={}, params=""):
-        """
-        Send a pre-signed POST request to the bitfinex api
-
-        @return response
-        """
-        url = '{}/{}'.format(self.host, endpoint)
-        sData = json.dumps(data)
-        headers = generate_auth_headers(
-            self.API_KEY, self.API_SECRET, endpoint, sData)
-        headers["content-type"] = "application/json"
-        async with aiohttp.ClientSession() as session:
-            async with session.post(url + params, headers=headers, data=sData) as resp:
-                text = await resp.text()
-                if resp.status < 200 or resp.status > 299:
-                    raise Exception('POST {} failed with status {} - {}'
-                                    .format(url, resp.status, text))
-                parsed = json.loads(text, parse_float=self.parse_float)
-                return parsed
-
-    ##################################################
-    #                  Public Data                   #
-    ##################################################
-
-    async def get_seed_candles(self, symbol, tf='1m', start=None, end=None, limit=10000, sort=0):
-        """
-        Get all of the seed candles between the start and end period.
-        # Attributes
-        @param symbol symbol string: pair symbol i.e tBTCUSD
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param tf int: timeframe inbetween candles i.e 1m (min), ..., 1D (day),
-                      ... 1M (month)
-        @param limit int: max number of items in response (max. 10000)
-        @param sort int: if = 1 it sorts results returned with old > new
-        @return Array [ MTS, OPEN, CLOSE, HIGH, LOW, VOLUME ]
-        """
-
-        if not start and not end:
-            start = 0
-            end = int(round((time.time() // 60 * 60) * 1000))
-
-        endpoint = f'candles/trade:{tf}:{symbol}/hist?limit={limit}&start={start}&end={end}&sort={sort}'
-        self.logger.info("Downloading seed candles from Bitfinex...")
-        candles = await self.fetch(endpoint)
-        self.logger.info("Downloaded {} candles.".format(len(candles)))
-        return candles
-
-    async def get_public_candles(self, symbol, start, end, section='hist',
-                                 tf='1m', limit=100, sort=-1):
-        """
-        Get all of the public candles between the start and end period.
-
-        # Attributes
-        @param symbol symbol string: pair symbol i.e tBTCUSD
-        @param secton string: available values: "last", "hist"
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param limit int: max number of items in response
-        @param tf int: timeframe inbetween candles i.e 1m (min), ..., 1D (day), 
-                      ... 1M (month)
-        @param sort int: if = 1 it sorts results returned with old > new
-        @return Array [ MTS, OPEN, CLOSE, HIGH, LOW, VOLUME ]
-        """
-        endpoint = "candles/trade:{}:{}/{}".format(tf, symbol, section)
-        params = "?start={}&end={}&limit={}&sort={}".format(
-            start, end, limit, sort)
-        candles = await self.fetch(endpoint, params=params)
-        return candles
-
-    async def get_public_trades(self, symbol, start, end, limit=100, sort=-1):
-        """
-        Get all of the public trades between the start and end period.
-
-        # Attributes
-        @param symbol symbol string: pair symbol i.e tBTCUSD
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param limit int: max number of items in response
-        @return Array [ ID, MTS, AMOUNT, RATE, PERIOD? ]
-        """
-        endpoint = "trades/{}/hist".format(symbol)
-        params = "?start={}&end={}&limit={}&sort={}".format(
-            start, end, limit, sort)
-        trades = await self.fetch(endpoint, params=params)
-        return sorted(trades, key=lambda x: (x[1], x[0]), reverse=True if sort == 1 else False)
-
-    async def get_public_books(self, symbol, precision="P0", length=25):
-        """
-        Get the public orderbook for a given symbol.
-
-        # Attributes
-        @param symbol symbol string: pair symbol i.e tBTCUSD
-        @param precision string: level of price aggregation (P0, P1, P2, P3, P4, R0)
-        @param length int: number of price points ("25", "100")
-        @return Array [ PRICE, COUNT, AMOUNT ]
-        """
-        endpoint = "book/{}/{}".format(symbol, precision)
-        params = "?len={}".format(length)
-        books = await self.fetch(endpoint, params)
-        return books
-
-    async def get_public_ticker(self, symbol):
-        """
-        Get tickers for the given symbol. Tickers shows you the current best bid and ask,
-        as well as the last trade price.
-
-        # Attributes
-        @param symbols symbol string: pair symbol i.e tBTCUSD
-        @return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE,
-          DAILY_CHANGE_PERC, LAST_PRICE, VOLUME, HIGH, LOW ]
-        """
-        endpoint = "ticker/{}".format(symbol)
-        ticker = await self.fetch(endpoint)
-        return ticker
-
-    async def get_public_tickers(self, symbols):
-        """
-        Get tickers for the given symbols. Tickers shows you the current best bid and ask,
-        as well as the last trade price.
-
-        # Attributes
-        @param symbols Array<string>: array of symbols i.e [tBTCUSD, tETHUSD]
-        @return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE,  DAILY_CHANGE_PERC,
-          LAST_PRICE, VOLUME, HIGH, LOW ]
-        """
-        endpoint = "tickers/?symbols={}".format(','.join(symbols))
-        ticker = await self.fetch(endpoint)
-        return ticker
-
-    async def get_public_tickers_history(self, symbols):
-        """
-        History of recent tickers.
-        Provides historic data of the best bid and ask at a 10-second interval.
-
-        # Attributes
-        @param symbols Array<string>: array of symbols i.e [tBTCUSD, tETHUSD]
-        @return Array [[ SYMBOL, BID, PLACEHOLDER, ASK, PLACEHOLDER, PLACEHOLDER,
-        PLACEHOLDER, PLACEHOLDER, PLACEHOLDER, PLACEHOLDER, PLACEHOLDER, PLACEHOLDER, MTS ], ...]
-        """
-        endpoint = "tickers/hist?symbols={}".format(','.join(symbols))
-        ticker = await self.fetch(endpoint)
-        return ticker
-
-    async def get_derivative_status(self, symbol):
-        """
-        Gets platform information for derivative symbol.
-
-        # Attributes
-        @param derivativeSymbol string: i.e tBTCF0:USTF0
-        @return [KEY/SYMBOL, MTS, PLACEHOLDER, DERIV_PRICE, SPOT_PRICE, PLACEHOLDER, INSURANCE_FUND_BALANCE4,
-            PLACEHOLDER, PLACEHOLDER, FUNDING_ACCRUED, FUNDING_STEP, PLACEHOLDER]
-        """
-        statuses = await self.get_derivative_statuses([symbol])
-        if len(statuses) > 0:
-            return statuses[0]
-        return []
-
-    async def get_derivative_statuses(self, symbols):
-        """
-        Gets platform information for a collection of derivative symbols.
-
-        # Attributes
-        @param derivativeSymbols Array<string>: array of symbols i.e [tBTCF0:USTF0 ...] or ["ALL"]
-        @return [KEY/SYMBOL, MTS, PLACEHOLDER, DERIV_PRICE, SPOT_PRICE, PLACEHOLDER, INSURANCE_FUND_BALANCE4,
-            PLACEHOLDER, PLACEHOLDER, FUNDING_ACCRUED, FUNDING_STEP, PLACEHOLDER]
-        """
-        endpoint = "status/deriv?keys={}".format(','.join(symbols))
-        status = await self.fetch(endpoint)
-        return status
-
-    async def get_liquidations(self, start, end, limit=100, sort=-1):
-        """
-        Endpoint to retrieve liquidations. By default it will retrieve the most recent liquidations,
-        but time-specific data can be retrieved using timestamps.
-
-        # Attributes
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param limit int: max number of items in response (max. 500)
-        @param sort int: if = 1 it sorts results returned with old > new
-        @return Array [ POS_ID, MTS, SYMBOL, AMOUNT, BASE_PRICE, IS_MATCH, IS_MARKET_SOLD, PRICE_ACQUIRED ]
-        """
-        endpoint = "liquidations/hist"
-        params = "?start={}&end={}&limit={}&sort={}".format(
-            start, end, limit, sort)
-        liquidations = await self.fetch(endpoint, params=params)
-        return liquidations
-
-    async def get_public_pulse_hist(self, end=None, limit=25):
-        """
-        View the latest pulse messages. You can specify an end timestamp to view older messages.
-
-        # Attributes
-        @param end int: millisecond end time
-        @param limit int: max number of items in response (MAX: 100)
-        @return Array [ PID, MTS, _PLACEHOLDER, PUID, _PLACEHOLDER, TITLE, CONTENT,
-            _PLACEHOLDER, _PLACEHOLDER, IS_PIN, IS_PUBLIC, COMMENTS_DISABLED, TAGS,
-            META, LIKES, _PLACEHOLDER, _PLACEHOLDER, [ PUID, MTS, _PLACEHOLDER,
-            NICKNAME, PLACEHOLDER, PICTURE, TEXT, _PLACEHOLDER, _PLACEHOLDER, TWITTER_HANDLE,
-            _PLACEHOLDER, FOLLOWERS, FOLLOWING, _PLACEHOLDER, _PLACEHOLDER, _PLACEHOLDER,
-            TIPPING_STATUS ] ], COMMENTS, _PLACEHOLDER, _PLACEHOLDER ]
-        """
-        endpoint = f"pulse/hist?limit={limit}"
-        if end:
-            endpoint += f'&end={end}'
-        hist = await self.fetch(endpoint)
-        return hist
-
-    async def get_public_pulse_profile(self, nickname='Bitfinex'):
-        """
-        This endpoint shows details for a specific Pulse profile
-
-        # Attributes
-        @param nickname string
-        @return Array [ PUID, MTS, _PLACEHOLDER, NICKNAME, _PLACEHOLDER, PICTURE,
-        TEXT, _PLACEHOLDER, _PLACEHOLDER, TWITTER_HANDLE, _PLACEHOLDER, FOLLOWERS,
-        FOLLOWING, _PLACEHOLDER, _PLACEHOLDER, _PLACEHOLDER, TIPPING_STATUS]
-        """
-        endpoint = f"pulse/profile/{nickname}"
-        profile = await self.fetch(endpoint)
-        return profile
-
-    async def get_market_average_price(self, symbol, amount=None, period=None, rate_limit=None):
-        """
-        Calculate the average execution price for Trading or rate for Margin funding.
-
-        # Attributes
-        @param symbol str: the symbol you want information about
-        @param amount str: amount. Positive for buy, negative for sell
-        @param period int: maximum period for Margin Funding
-        @param rate_limit string: limit rate/price (ex. "1000.5")
-
-        @return Array
-        For exchange trading
-        [PRICE_AVG, AMOUNT]
-
-        For funding
-        [RATE_AVG, AMOUNT]
-        """
-        endpoint = f"calc/trade/avg"
-        payload = {
-            "symbol": symbol,
-            "amount": amount,
-            "period": period,
-            "rate_limit": rate_limit
-        }
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def get_foreign_exchange_rate(self, ccy1, ccy2):
-        """
-        Calculate the average execution price for Trading or rate for Margin funding.
-
-        # Attributes
-        @param ccy1 str: base currency
-        @param ccy2 str: quote currency
-
-        @return Array [ CURRENT_RATE ]
-        """
-        endpoint = f"calc/fx"
-        payload = {
-            "ccy1": ccy1,
-            "ccy2": ccy2
-        }
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def get_public_stats(self, key, size, symbol, section, side=None,
-                               sort=None, start=None, end=None, limit=None):
-        """
-        The Stats endpoint provides various statistics on a specified trading pair or funding currency.
-
-        # Attributes
-        @param key str
-        Available values -> "funding.size", "credits.size", "credits.size.sym",
-        "pos.size", "vol.1d", "vol.7d", "vol.30d", "vwap"
-
-        @param size str
-        Available values -> "30m", "1d", '1m'
-
-        @param symbol str: the symbol you want information about
-        (e.g. tBTCUSD, tETHUSD, fUSD, fBTC)
-
-        @param section str
-        Available values -> "last", "hist"
-
-        @param side str: only for non-funding queries
-        Available values -> "long", "short"
-
-        @param sort int: if = 1 it sorts results returned with old > new
-
-        @param start str: millisecond start time
-
-        @param end str: millisecond end time
-
-        @param limit int: number of records (max 10000)
-
-        @return
-        Array [MTS, VALUE] with Section = "last"
-        Array [[MTS, VALUE], ...] with Section = "hist"
-        """
-        if key != 'funding.size' and not side:
-            raise Exception('side is compulsory for non funding queries')
-        endpoint = f"stats1/{key}:{size}:{symbol}"
-        if side:
-            endpoint += f":{side}"
-        if section:
-            endpoint += f"/{section}"
-        endpoint += '?'
-        if sort:
-            endpoint += f"sort={sort}&"
-        if start:
-            endpoint += f"start={start}&"
-        if end:
-            endpoint += f"end={end}&"
-        if limit:
-            endpoint += f"limit={limit}"
-        message = await self.fetch(endpoint)
-        return message
-
-
-    async def get_public_funding_stats(self, symbol):
-        """
-        Get a list of the most recent funding data for the given currency
-        (FRR, average period, total amount provided, total amount used)
-
-        # Attributes
-        @param limit int: number of results (max 250)
-        @param start str: millisecond start time
-        @param end str: millisecond end time
-
-        @return Array
-        [[ TIMESTAMP,  PLACEHOLDER, PLACEHOLDER, FRR, AVG_PERIOD, PLACEHOLDER,
-        PLACEHOLDER, FUNDING_AMOUNT, FUNDING_AMOUNT_USED ], ...]
-        """
-        endpoint = f"funding/stats/{symbol}/hist"
-        stats = await self.fetch(endpoint)
-        return stats
-
-    async def get_conf_list_pair_exchange(self):
-        """
-        Get list of available exchange pairs
-        # Attributes
-        @return Array [ SYMBOL ]
-        """
-        endpoint = "conf/pub:list:pair:exchange"
-        pairs = await self.fetch(endpoint)
-        return pairs
-
-    ##################################################
-    #               Authenticated Data               #
-    ##################################################
-
-    async def get_wallets(self):
-        """
-        Get all wallets on account associated with API_KEY - Requires authentication.
-
-        @return Array <models.Wallet>
-        """
-        endpoint = "auth/r/wallets"
-        raw_wallets = await self.post(endpoint)
-        return [Wallet(*rw[:5]) for rw in raw_wallets]
-
-    async def get_margin_info(self, symbol='base'):
-        """
-        Get account margin information (like P/L, Swaps, Margin Balance, Tradable Balance and others).
-        Use different keys (base, SYMBOL, sym_all) to retrieve different kinds of data.
-
-        @return Array
-        """
-        endpoint = f"auth/r/info/margin/{symbol}"
-        raw_margin_info = await self.post(endpoint)
-        if symbol == 'base':
-            return MarginInfoBase.from_raw_margin_info(raw_margin_info)
-        elif symbol == 'sym_all':
-            return [MarginInfo.from_raw_margin_info(record) for record in raw_margin_info]
-        else:
-            return MarginInfo.from_raw_margin_info(raw_margin_info)
-
-    async def get_active_orders(self, symbol):
-        """
-        Get all of the active orders associated with API_KEY - Requires authentication.
-
-        # Attributes
-        @param symbol string: pair symbol i.e tBTCUSD
-        @return Array <models.Order>
-        """
-        endpoint = "auth/r/orders/{}".format(symbol)
-        raw_orders = await self.post(endpoint)
-        return [Order.from_raw_order(ro) for ro in raw_orders]
-
-    async def get_order_history(self, symbol, start, end, limit=25, sort=-1, ids=None):
-        """
-        Get all of the orders between the start and end period associated with API_KEY
-        - Requires authentication.
-
-        # Attributes
-        @param symbol string: pair symbol i.e tBTCUSD
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param limit int: max number of items in response
-        @param ids list of int: allows you to retrieve specific orders by order ID (ids: [ID1, ID2, ID3])
-        @return Array <models.Order>
-        """
-        endpoint = "auth/r/orders/{}/hist".format(symbol)
-        payload = {}
-        if start:
-            payload['start'] = start
-        if end:
-            payload['end'] = end
-        if limit:
-            payload['limit'] = limit
-        if sort:
-            payload['sort'] = sort
-        if ids:
-            payload['id'] = ids
-        raw_orders = await self.post(endpoint, payload)
-        return [Order.from_raw_order(ro) for ro in raw_orders]
-
-    async def get_active_position(self):
-        """
-        Get all of the active position associated with API_KEY - Requires authentication.
-
-        @return Array <models.Position>
-        """
-        endpoint = "auth/r/positions"
-        raw_positions = await self.post(endpoint)
-        return [Position.from_raw_rest_position(rp) for rp in raw_positions]
-
-    async def get_order_trades(self, symbol, order_id):
-        """
-        Get all of the trades that have been generated by the given order associated with API_KEY
-        - Requires authentication.
-
-        # Attributes
-        @param symbol string: pair symbol i.e tBTCUSD
-        @param order_id string: id of the order
-        @return Array <models.Trade>
-        """
-        endpoint = "auth/r/order/{}:{}/trades".format(symbol, order_id)
-        raw_trades = await self.post(endpoint)
-        return [Trade.from_raw_rest_trade(rt) for rt in raw_trades]
-
-    async def get_trades(self, start, end, symbol=None, limit=25):
-        """
-        Get all of the trades between the start and end period associated with API_KEY
-        - Requires authentication.
-
-        # Attributes
-        @param symbol string: pair symbol i.e tBTCUSD
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param limit int: max number of items in response
-        @return Array <models.Trade>
-        """
-        endpoint = "auth/r/trades/{}/hist".format(symbol) if symbol else "auth/r/trades/hist"
-        params = "?start={}&end={}&limit={}".format(start, end, limit)
-        raw_trades = await self.post(endpoint, params=params)
-        return [Trade.from_raw_rest_trade(rt) for rt in raw_trades]
-
-    async def get_funding_trades(self, symbol, start, end, limit=25):
-        """
-        Get all of the funding trades between the start and end period associated with API_KEY
-        - Requires authentication.
-
-        # Attributes
-        @param symbol string: pair symbol i.e fUSD
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param limit int: max number of items in response
-        @return Array <models.FundingTrade>
-        """
-        endpoint = "auth/r/funding/trades/{}/hist".format(symbol)
-        raw_trades = await self.post(endpoint)
-        return [FundingTrade.from_raw_rest_trade(rt) for rt in raw_trades]
-
-    async def get_funding_offers(self, symbol):
-        """
-        Get all of the funding offers associated with API_KEY - Requires authentication.
-
-        @return Array <models.FundingOffer>
-        """
-        endpoint = "auth/r/funding/offers/{}".format(symbol)
-        offers = await self.post(endpoint)
-        return [FundingOffer.from_raw_offer(o) for o in offers]
-
-    async def get_funding_offer_history(self, symbol, start, end, limit=25):
-        """
-        Get all of the funding offers between the start and end period associated with API_KEY
-        - Requires authentication.
-
-        # Attributes
-        @param symbol string: pair symbol i.e tBTCUSD
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param limit int: max number of items in response
-        @return Array <models.FundingOffer>
-        """
-        endpoint = "auth/r/funding/offers/{}/hist".format(symbol)
-        params = "?start={}&end={}&limit={}".format(start, end, limit)
-        offers = await self.post(endpoint, params=params)
-        return [FundingOffer.from_raw_offer(o) for o in offers]
-
-    async def get_funding_loans(self, symbol):
-        """
-        Get all of the funding loans associated with API_KEY - Requires authentication.
-
-        @return Array <models.FundingLoan>
-        """
-        endpoint = "auth/r/funding/loans/{}".format(symbol)
-        loans = await self.post(endpoint)
-        return [FundingLoan.from_raw_loan(o) for o in loans]
-
-    async def get_funding_loan_history(self, symbol, start, end, limit=25):
-        """
-        Get all of the funding loans between the start and end period associated with API_KEY
-        - Requires authentication.
-
-        # Attributes
-        @param symbol string: pair symbol i.e tBTCUSD
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param limit int: max number of items in response
-        @return Array <models.FundingLoan>
-        """
-        endpoint = "auth/r/funding/loans/{}/hist".format(symbol)
-        params = "?start={}&end={}&limit={}".format(start, end, limit)
-        loans = await self.post(endpoint, params=params)
-        return [FundingLoan.from_raw_loan(o) for o in loans]
-
-    async def get_funding_credits(self, symbol):
-        endpoint = "auth/r/funding/credits/{}".format(symbol)
-        credits = await self.post(endpoint)
-        return [FundingCredit.from_raw_credit(c) for c in credits]
-
-    async def get_funding_credit_history(self, symbol, start, end, limit=25):
-        """
-        Get all of the funding credits between the start and end period associated with API_KEY
-        - Requires authentication.
-
-        # Attributes
-        @param symbol string: pair symbol i.e tBTCUSD
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param limit int: max number of items in response
-        @return Array <models.FundingCredit>
-        """
-        endpoint = "auth/r/funding/credits/{}/hist".format(symbol)
-        params = "?start={}&end={}&limit={}".format(start, end, limit)
-        credits = await self.post(endpoint, params=params)
-        return [FundingCredit.from_raw_credit(c) for c in credits]
-
-    async def get_ledgers(self, symbol, start, end, limit=25, category=None):
-        """
-        Get all ledgers on account associated with API_KEY - Requires authentication.
-
-        You can emit the symbol param in order to receive ledger entries for all symbols.
-        See category filters here: https://docs.bitfinex.com/reference#rest-auth-ledgers
-
-        # Attributes
-        @param symbol string: pair symbol i.e tBTCUSD - can be omitted to receive all entries
-        @param start int: start of window
-        @param end int: end of window
-        @param limit int: max number of entries
-        @param category int: filter category to receive specific ledger entries
-
-        @return Array <models.Ledger>
-        """
-        endpoint = ("auth/r/ledgers/{}/hist".format(symbol)
-                    if symbol else "auth/r/ledgers/hist")
-        params = "?start={}&end={}&limit={}".format(start, end, limit)
-        if category:
-            payload = {"category": category}
-            raw_ledgers = await self.post(endpoint, payload, params=params)
-        else:
-            raw_ledgers = await self.post(endpoint, params=params)
-        return [Ledger.from_raw_ledger(rl) for rl in raw_ledgers]
-
-    async def get_movement_history(self, currency, start="", end="", limit=25):
-        """
-        Get all of the deposits and withdraws between the start and end period associated with API_KEY
-        - Requires authentication.
-        # Attributes
-        @param currency string: pair symbol i.e BTC
-        @param start int: millisecond start time
-        @param end int: millisecond end time
-        @param limit int: max number of items in response
-        @return Array <models.Movement>
-        """
-        endpoint = "auth/r/movements/{}/hist".format(currency)
-        params = "?start={}&end={}&limit={}".format(start, end, limit)
-        raw_movements = await self.post(endpoint, params=params)
-        return [Movement.from_raw_movement(rm) for rm in raw_movements]
-
-    async def submit_funding_offer(self, symbol, amount, rate, period,
-                                   funding_type=FundingOffer.Type.LIMIT, hidden=False):
-        """
-        Submits a new funding offer
-
-        # Attributes
-        @param symbol string: pair symbol i.e fUSD
-        @param amount float: funding size
-        @param rate float: percentage rate to charge per a day
-        @param period int: number of days for funding to remain active once accepted
-        """
-        payload = {
-            "type": funding_type,
-            "symbol": symbol,
-            "amount": str(amount),
-            "rate": str(rate),
-            "period": period,
-        }
-        # calculate and add flags
-        flags = calculate_order_flags(hidden, None, None, None, None)
-        payload['flags'] = flags
-        endpoint = "auth/w/funding/offer/submit"
-        raw_notification = await self.post(endpoint, payload)
-        return Notification.from_raw_notification(raw_notification)
-
-    async def submit_cancel_funding_offer(self, fundingId):
-        """
-        Cancel a funding offer
-
-        # Attributes
-        @param fundingId int: the id of the funding offer
-        """
-        endpoint = "auth/w/funding/offer/cancel"
-        raw_notification = await self.post(endpoint, {'id': fundingId})
-        return Notification.from_raw_notification(raw_notification)
-
-    async def submit_cancel_all_funding_offer(self, currency):
-        """
-        Cancel all funding offers at once
-
-        # Attributes
-        @param currency str: currency for which to cancel all offers (USD, BTC, UST ...)
-        """
-        endpoint = "auth/w/funding/offer/cancel/all"
-        raw_notification = await self.post(endpoint, {'currency': currency})
-        return Notification.from_raw_notification(raw_notification)
-
-    async def keep_funding(self, type, id):
-        """
-        Toggle to keep funding taken. Specify loan for unused funding and credit for used funding.
-
-        # Attributes
-        @param type string: funding type ('credit' or 'loan')
-        @param id int: loan or credit identifier
-        """
-        endpoint = "auth/w/funding/keep"
-        payload = {
-            "type": type,
-            "id": id
-        }
-        raw_notification = await self.post(endpoint, payload)
-        return Notification.from_raw_notification(raw_notification)
-
-    async def submit_wallet_transfer(self, from_wallet, to_wallet, from_currency, to_currency, amount):
-        """
-        Transfer funds between wallets
-
-        # Attributes
-        @param from_wallet string: wallet name to transfer from i.e margin, exchange
-        @param to_wallet string: wallet name to transfer to i.e margin, exchange
-        @param from_currency string: currency symbol to tranfer from i.e BTC, USD
-        @param to_currency string: currency symbol to transfer to i.e BTC, USD
-        @param amount float: amount of funds to transfer
-        """
-        endpoint = "auth/w/transfer"
-        payload = {
-            "from": from_wallet,
-            "to": to_wallet,
-            "currency": from_currency,
-            "currency_to": to_currency,
-            "amount": str(amount),
-        }
-        raw_transfer = await self.post(endpoint, payload)
-        return Notification.from_raw_notification(raw_transfer)
-
-    async def get_wallet_deposit_address(self, wallet, method, renew=0):
-        """
-        Get the deposit address for the given wallet and protocol
-
-        # Attributes
-        @param wallet string: wallet name i.e margin, exchange
-        @param method string: transfer protocol i.e bitcoin
-        """
-        endpoint = "auth/w/deposit/address"
-        payload = {
-            "wallet": wallet,
-            "method": method,
-            "op_renew": renew,
-        }
-        raw_deposit = await self.post(endpoint, payload)
-        return Notification.from_raw_notification(raw_deposit)
-
-    async def create_wallet_deposit_address(self, wallet, method):
-        """
-        Creates a new deposit address for the given wallet and protocol.
-        Previously generated addresses remain linked.
-
-        # Attributes
-        @param wallet string: wallet name i.e margin, exchange
-        @param method string: transfer protocol i.e bitcoin
-        """
-        return await self.get_wallet_deposit_address(wallet, method, renew=1)
-
-    async def submit_wallet_withdraw(self, wallet, method, amount, address):
-        """
-        Submits a request to withdraw crypto funds to an external wallet.
-
-        # Attributes
-        @param wallet string: wallet name i.e margin, exchange
-        @param method string: transfer protocol i.e bitcoin
-        @param amount float: amount of funds to withdraw
-        @param address string: external address to withdraw to
-        """
-        endpoint = "auth/w/withdraw"
-        payload = {
-            "wallet": wallet,
-            "method": method,
-            "amount": str(amount),
-            "address": str(address)
-        }
-        raw_deposit = await self.post(endpoint, payload)
-        return Notification.from_raw_notification(raw_deposit)
-
-    # async def submit_close_funding(self, id, type):
-    #     """
-    #     `/v2/auth/w/funding/close` (params: `id`, `type` (credit|loan))
-    #     """
-    #     pass
-
-    # async def submit_auto_funding(self, ):
-    #     """
-    #     `/v2/auth/w/funding/auto` (params: `status` (1|0), `currency`, `amount`, `rate`, `period`)
-    #     (`rate === 0` means `FRR`)
-    #     """
-    #     pass
-
-    ##################################################
-    #                    Orders                      #
-    ##################################################
-
-    async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT,
-                           hidden=False, price_trailing=None, price_aux_limit=None,
-                           oco_stop_price=None, close=False, reduce_only=False,
-                           post_only=False, oco=False, aff_code=None, time_in_force=None,
-                           leverage=None, gid=None):
-        """
-        Submit a new order
-
-        # Attributes
-        @param gid: assign the order to a group identifier
-        @param symbol: the name of the symbol i.e 'tBTCUSD
-        @param price: the price you want to buy/sell at (must be positive)
-        @param amount: order size: how much you want to buy/sell,
-          a negative amount indicates a sell order and positive a buy order
-        @param market_type	Order.Type: please see Order.Type enum
-          amount	decimal string	Positive for buy, Negative for sell
-        @param hidden: if True, order should be hidden from orderbooks
-        @param price_trailing:	decimal trailing price
-        @param price_aux_limit:	decimal	auxiliary Limit price (only for STOP LIMIT)
-        @param oco_stop_price: set the oco stop price (requires oco = True)
-        @param close: if True, close position if position present
-        @param reduce_only: if True, ensures that the executed order does not flip the opened position
-        @param post_only: if True, ensures the limit order will be added to the order book and not
-          match with a pre-existing order
-        @param oco: cancels other order option allows you to place a pair of orders stipulating
-          that if one order is executed fully or partially, then the other is automatically canceled
-        @param aff_code: bitfinex affiliate code
-        @param time_in_force:	datetime for automatic order cancellation ie. 2020-01-01 10:45:23
-        @param leverage: the amount of leverage to apply to the order as an integer
-        """
-        cid = gen_unique_cid()
-        payload = {
-            "cid": cid,
-            "type": str(market_type),
-            "symbol": symbol,
-            "amount": str(amount),
-            "price": str(price),
-            "meta": {}
-        }
-        # calculate and add flags
-        flags = calculate_order_flags(hidden, close, reduce_only, post_only, oco)
-        payload['flags'] = flags
-        # add extra parameters
-        if price_trailing != None:
-            payload['price_trailing'] = price_trailing
-        if price_aux_limit != None:
-            payload['price_aux_limit'] = price_aux_limit
-        if oco_stop_price != None:
-            payload['price_oco_stop'] = str(oco_stop_price)
-        if time_in_force != None:
-            payload['tif'] = time_in_force
-        if gid != None:
-            payload['gid'] = gid
-        if leverage != None:
-            payload['lev'] = str(leverage)
-        if aff_code != None:
-            payload['meta']['aff_code'] = str(aff_code)
-        endpoint = "auth/w/order/submit"
-        raw_notification = await self.post(endpoint, payload)
-        return Notification.from_raw_notification(raw_notification)
-
-    async def submit_cancel_order(self, orderId):
-        """
-        Cancel an existing open order
-
-        # Attributes
-        @param orderId: the id of the order that you want to update
-        """
-        endpoint = "auth/w/order/cancel"
-        raw_notification = await self.post(endpoint, {'id': orderId})
-        return Notification.from_raw_notification(raw_notification)
-
-    async def submit_cancel_order_multi(self, ids=None, cids=None, gids=None, all=None):
-        """
-        Cancel multiple orders simultaneously. Orders can be canceled based on the Order ID,
-        the combination of Client Order ID and Client Order Date, or the Group Order ID.
-        Alternatively, the body param 'all' can be used with a value of 1 to cancel all orders.
-
-        # Attributes
-        @param id array of int: orders ids
-        [1234, 1235, ...]
-
-        @param cids array of arrays: client orders ids
-        [[234, "2016-12-05"], ...]
-
-        @param gids array of int: group orders id
-        [11, ...]
-
-        @param all int: cancel all open orders if value is set to 1
-        """
-        endpoint = "auth/w/order/cancel/multi"
-        payload = {}
-        if ids != None:
-            payload["id"] = ids
-        if cids != None:
-            payload["cid"] = cids
-        if gids != None:
-            payload["gid"] = gids
-        if all != None:
-            payload["all"] = all
-        raw_notification = await self.post(endpoint, payload)
-        return Notification.from_raw_notification(raw_notification)
-
-    async def submit_update_order(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None,
-                                  price_trailing=None, hidden=False, close=False, reduce_only=False,
-                                  post_only=False, time_in_force=None, leverage=None):
-        """
-        Update an existing order
-
-        # Attributes
-        @param orderId: the id of the order that you want to update
-        @param price: the price you want to buy/sell at (must be positive)
-        @param amount: order size: how much you want to buy/sell,
-          a negative amount indicates a sell order and positive a buy order
-        @param delta:	change of amount
-        @param price_trailing:	decimal trailing price
-        @param price_aux_limit:	decimal	auxiliary Limit price (only for STOP LIMIT)
-        @param hidden: if True, order should be hidden from orderbooks
-        @param close: if True, close position if position present
-        @param reduce_only: if True, ensures that the executed order does not flip the opened position
-        @param post_only: if True, ensures the limit order will be added to the order book and not
-          match with a pre-existing order
-        @param time_in_force:	datetime for automatic order cancellation ie. 2020-01-01 10:45:23
-        @param leverage: the amount of leverage to apply to the order as an integer
-        """
-        payload = {"id": orderId}
-        if price != None:
-            payload['price'] = str(price)
-        if amount != None:
-            payload['amount'] = str(amount)
-        if delta != None:
-            payload['delta'] = str(delta)
-        if price_aux_limit != None:
-            payload['price_aux_limit'] = str(price_aux_limit)
-        if price_trailing != None:
-            payload['price_trailing'] = str(price_trailing)
-        if time_in_force != None:
-            payload['tif'] = str(time_in_force)
-        if leverage != None:
-            payload["lev"] = str(leverage)
-        flags = calculate_order_flags(
-            hidden, close, reduce_only, post_only, False)
-        payload['flags'] = flags
-        endpoint = "auth/w/order/update"
-        raw_notification = await self.post(endpoint, payload)
-        return Notification.from_raw_notification(raw_notification)
-
-    async def submit_order_multi_op(self, orders):
-        """
-        Send Multiple order-related operations.
-        Please note the sent object has only one property with a value
-        of an array of arrays detailing each order operation.
-
-        https://docs.bitfinex.com/reference#rest-auth-order-multi
-
-        Expected orders ->
-        [
-            ["on", {  // Order Submit
-                type: "EXCHANGE LIMIT",
-                symbol: "tBTCUSD",
-                price: "123.45",
-                amount: "1.2345",
-                flags: 0
-        	}],
-        	["oc", { ... }],
-        	...
-        ]
-
-        @param type string
-        Available values -> LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET,
-        STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT, TRAILING STOP,
-        EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK, IOC, EXCHANGE IOC
-
-        @param symbol string: symbol of order
-
-        @param price string: price of order
-
-        @param amount string: amount of order (positive for buy, negative for sell)
-
-        @param flags int: (optional) see https://docs.bitfinex.com/v2/docs/flag-values
-
-        @param lev int: set the leverage for a derivative order, supported
-        by derivative symbol orders only. The value should be between 1 and
-        100 inclusive. The field is optional, if omitted the default leverage value of 10 will be used.
-
-        @param price_trailing string: the trailing price for a trailing stop order
-
-        @param price_aux_limit string: auxiliary Limit price (for STOP LIMIT)
-
-        @param price_oco_stop string: OCO stop price
-
-        @param gid int: group order id
-
-        @param tif string: Time-In-Force - datetime for automatic order cancellation (YYYY-MM-DD HH:MM:SS)
-
-        @param id int: Order ID (can be retrieved by calling the Retrieve Orders endpoint)
-
-        @param cid int: Client Order ID
-
-        @param cid_date string: Client Order ID Date (YYYY-MM-DD)
-
-        @param all int: cancel all open orders if value is set to 1
-        """
-        payload = {"ops": orders}
-        endpoint = "auth/w/order/multi"
-        raw_notification = await self.post(endpoint, payload)
-        return Notification.from_raw_notification(raw_notification)
-
-    async def claim_position(self, position_id, amount):
-        """
-        The claim feature allows the use of funds you have in your Margin Wallet
-        to settle a leveraged position as an exchange buy or sale
-
-        # Attributes
-        @param position_id: id of the position
-        @param amount: amount to claim
-        @return Array [ MTS, TYPE, MESSAGE_ID, null, [SYMBOL, POSITION_STATUS,
-        AMOUNT, BASE_PRICE, MARGIN_FUNDING, MARGIN_FUNDING_TYPE, PLACEHOLDER,
-        PLACEHOLDER, PLACEHOLDER, PLACEHOLDER, PLACEHOLDER, POSITION_ID, MTS_CREATE,
-        MTS_UPDATE, PLACEHOLDER, POS_TYPE, PLACEHOLDER, COLLATERAL, MIN_COLLATERAL,
-        META], CODE, STATUS, TEXT]
-        """
-        payload = {
-            "id": position_id,
-            "amount": f"{amount * -1}"
-        }
-        endpoint = "auth/w/position/claim"
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def get_alerts(self):
-        """
-        Retrieve a list of active price alerts
-        """
-        endpoint = f"auth/r/alerts"
-
-        message = await self.post(endpoint, {})
-        return message
-
-    async def set_alert(self, type, symbol, price):
-        """
-        Sets up a price alert at the given value
-
-        # Attributes
-        @param type string
-        @param symbol string
-        @param price float
-        """
-        endpoint = f"auth/w/alert/set"
-        payload = {
-            "type": type,
-            "symbol": symbol,
-            "price": price
-        }
-
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def delete_alert(self, symbol, price):
-        """
-        Delete an active alert
-
-        # Attributes
-        @param symbol string
-        @param price float
-        """
-        endpoint = f"auth/w/alert/price:{symbol}:{price}/del"
-        payload = {
-            "symbol": symbol,
-            "price": price
-        }
-
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def calc_order_avail(self, symbol, type, lev, dir=None, rate=None):
-        """
-        Calculate the balance available for orders/offers
-
-        # Attributes
-        @param symbol str: Symbol (tBTCUSD, tBTCUST, fUSD, .... )
-        @param dir int: Direction of the order (1 for by, -1 for sell) (Mandator for EXCHANGE and MARGIN type, not used for FUNDING)
-        @param rate str: Order price (Mandator for EXCHANGE and MARGIN type, not used for FUNDING)
-        @param type str: Type of the order/offer EXCHANGE, MARGIN, DERIV, or FUNDING
-        @param lev str: Leverage that you want to use in calculating the max order amount (DERIV only)
-        """
-        endpoint = f"auth/calc/order/avail"
-        payload = {
-            "symbol": symbol,
-            "type": type,
-            "lev": lev
-        }
-
-        if dir:
-            payload["dir"] = dir
-
-        if rate:
-            payload["rate"] = rate
-
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def write_user_settings(self, settings):
-        """
-        Allows you to create custom settings by creating key: value pairs
-
-        # Attributes
-        @param Settings object: object of keys and values to be set. Must follow regex pattern /^api:[A-Za-z0-9_-]*$/
-        """
-        endpoint = f"auth/w/settings/set"
-        payload = {
-            "Settings": settings
-        }
-
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def read_user_settings(self, keys):
-        """
-        Allows you to read custom settings by providing a key
-
-        # Attributes
-        @param Keys array: the keys for which you wish to retrieve the values
-        """
-        endpoint = f"auth/w/settings"
-        payload = {
-            "Keys": keys
-        }
-
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def delete_user_settings(self, settings):
-        """
-        Allows you to delete custom settings
-
-        # Attributes
-        @param settings object: object of keys to be deleted followed by value 1. Must follow regex pattern /^api:[A-Za-z0-9_-]*$/
-        """
-        endpoint = f"auth/w/settings/del"
-        payload = {
-            "Settings": settings
-        }
-
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def get_auth_pulse_hist(self, is_public=None):
-        """
-        Allows you to retrieve your private pulse history or the public pulse history with an additional UID_LIKED field.
-
-        # Attributes
-        @param is_public int: allows you to receive the public pulse history with the UID_LIKED field
-        @return Array [ PID, MTS, _PLACEHOLDER, PUID, _PLACEHOLDER, TITLE,
-        CONTENT, _PLACEHOLDER, _PLACEHOLDER, IS_PIN, IS_PUBLIC, COMMENTS_DISABLED,
-        TAGS, META,LIKES, UID_LIKED, _PLACEHOLDER, [ PUID, MTS, _PLACEHOLDER, NICKNAME,
-        _PLACEHOLDER, PICTURE, TEXT, _PLACEHOLDER, _PLACEHOLDER, TWITTER_HANDLE, _PLACEHOLDER,
-        FOLLOWERS, FOLLOWING, _PLACEHOLDER, _PLACEHOLDER, _PLACEHOLDER, TIPPING_STATUS ], COMMENTS,
-        _PLACEHOLDER, _PLACEHOLDER ]
-        """
-        endpoint = f"auth/r/pulse/hist"
-        if is_public:
-            endpoint += f'?isPublic={is_public}'
-        hist = await self.post(endpoint)
-        return hist
-
-    async def submit_pulse(self, title, content, parent=None, is_pin=False,
-                        attachments=[], disable_comments=False, is_public=True):
-        """
-        Allows you to write Pulse messages
-
-        # Attributes
-        @param title str: title of the message (min 16, max 120 characters)
-        @param content str: content of the message
-        @param parent str: Pulse Message ID (PID) of parent post
-        @param is_pin boolean: is message pinned?
-        @param attachments list of str: base64 format
-        @param disable_comments boolean: are comments disabled?
-        @param is_public boolean: is a public message?
-        @return Array [ PID, MTS, _PLACEHOLDER, PUID, _PLACEHOLDER, TITLE,
-        CONTENT, _PLACEHOLDER, _PLACEHOLDER, IS_PIN, IS_PUBLIC, COMMENTS_DISABLED,
-        TAGS // This inner array contains zero or more tag strings ATTACHMENTS, _PLACEHOLDER,
-        LIKES, UID_LIKED, _PLACEHOLDER, [], ... ]
-        """
-        endpoint = f"auth/w/pulse/add"
-        payload = {
-            "title": title,
-            "content": content,
-            "isPin": 1 if is_pin else 0,
-            "attachments": attachments,
-            "disableComments": 1 if disable_comments else 0,
-            "isPublic": 1 if is_public else 0
-        }
-        if parent:
-            payload["parent"] = parent
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def submit_pulse_comment(self, title, content, parent, is_pin=False,
-                        attachments=[], disable_comments=False, is_public=True):
-        """
-        Allows you to write a Pulse comment
-
-        # Attributes
-        @param title str: title of the message (min 16, max 120 characters)
-        @param content str: content of the message
-        @param parent str: Pulse Message ID (PID) of parent post
-        @param is_pin boolean: is message pinned?
-        @param attachments list of str: base64 format
-        @param disable_comments boolean: are comments disabled?
-        @param is_public boolean: is a public message?
-        @return Array [ PID, MTS, _PLACEHOLDER, PUID, _PLACEHOLDER, TITLE,
-        CONTENT, _PLACEHOLDER, _PLACEHOLDER, IS_PIN, IS_PUBLIC, COMMENTS_DISABLED,
-        TAGS // This inner array contains zero or more tag strings ATTACHMENTS, _PLACEHOLDER,
-        LIKES, UID_LIKED, _PLACEHOLDER, [], ... ]
-        """
-        endpoint = f"auth/w/pulse/add"
-        payload = {
-            "title": title,
-            "content": content,
-            "isPin": 1 if is_pin else 0,
-            "attachments": attachments,
-            "disableComments": 1 if disable_comments else 0,
-            "isPublic": 1 if is_public else 0,
-            "parent": parent
-        }
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def delete_pulse(self, pid):
-        """
-        Allows you to delete your Pulse messages
-
-        # Attributes
-        @param pid str: ID of the pulse message that you want to delete
-        @return Array [1] or [0]
-        """
-        endpoint = f"auth/w/pulse/del"
-        payload = {
-            'pid': pid
-        }
-        message = await self.post(endpoint, payload)
-        return message
-
-    async def generate_invoice(self, amount, wallet='exchange', currency='LNX'):
-        """
-        Generates a Lightning Network deposit invoice
-
-        # Attributes
-        @param wallet str: Select the wallet that will receive the invoice payment
-        Currently only 'exchange' is available
-        @param currency str: Select the currency for which you wish to generate an invoice
-        Currently only LNX (Bitcoin Lightning Network) is available.
-        @param amount str: Amount that you wish to deposit (in BTC; min 0.000001, max 0.02)
-
-        @return Array [INVOICE_HASH, INVOICE, PLACEHOLDER, PLACEHOLDER, AMOUNT]
-
-        If this is the first time you are generating an LNX invoice on your account,
-        you will first need to create a deposit address. To do this, call
-        self.get_wallet_deposit_address(method='LNX', wallet='exchange')
-        """
-        endpoint = f"auth/w/deposit/invoice"
-        payload = {
-            "wallet": wallet,
-            "currency": currency,
-            "amount": amount
-        }
-        message = await self.post(endpoint, payload)
-        return message
-
-    ##################################################
-    #                   Derivatives                  #
-    ##################################################
-
-    async def set_derivative_collateral(self, symbol, collateral):
-        """
-        Update the amount of callateral used to back a derivative position.
-
-        # Attributes
-        @param symbol of the derivative i.e 'tBTCF0:USTF0'
-        @param collateral: amount of collateral/value to apply to the open position
-        """
-        endpoint = 'auth/w/deriv/collateral/set'
-        payload = {}
-        payload['symbol'] = symbol
-        payload['collateral'] = collateral
-        return await self.post(endpoint, data=payload)
-
-    ##################################################
-    #                   Merchants                    #
-    ##################################################
-
-    async def submit_invoice(self, amount, currency, pay_currencies, order_id, webhook, redirect_url, customer_info_nationality,
-                             customer_info_resid_country, customer_info_resid_city, customer_info_resid_zip_code,
-                             customer_info_resid_street, customer_info_full_name, customer_info_email,
-                             customer_info_resid_state=None, customer_info_resid_building_no=None, duration=None):
-        """
-        Submit an invoice for payment
-
-        # Attributes
-        @param amount str: Invoice amount in currency (From 0.1 USD to 1000 USD)
-        @param currency str: Invoice currency, currently supported: USD
-        @param pay_currencies list of str: Currencies in which invoice accepts the payments, supported values are BTC, ETH, UST-ETH, UST-TRX, UST-LBT, LNX, LBT
-        @param order_id str: Reference order identifier in merchant's platform
-        @param webhook str: The endpoint that will be called once the payment is completed/expired
-        @param redirect_url str: Merchant redirect URL, this one is used in UI to redirect customer to merchant's site once the payment is completed/expired
-        @param customer_info_nationality str: Customer's nationality, alpha2 code or full country name (alpha2 preffered)
-        @param customer_info_resid_country str: Customer's residential country, alpha2 code or full country name (alpha2 preffered)
-        @param customer_info_resid_city str: Customer's residential city/town
-        @param customer_info_resid_zip_code str: Customer's residential zip code/postal code
-        @param customer_info_resid_street str: Customer's residential street address
-        @param customer_info_full_name str: Customer's full name
-        @param customer_info_email str: Customer's email address
-        @param customer_info_resid_state str: Optional, customer's residential state/province
-        @param customer_info_resid_building_no str: Optional, customer's residential building number/name
-        @param duration int: Optional, invoice expire time in seconds, minimal duration is 5 mins (300) and maximal duration is 24 hours (86400). Default value is 15 minutes
-        """
-        endpoint = 'auth/w/ext/pay/invoice/create'
-        payload = {
-            'amount': amount,
-            'currency': currency,
-            'payCurrencies': pay_currencies,
-            'orderId': order_id,
-            'webhook': webhook,
-            'redirectUrl': redirect_url,
-            'customerInfo': {
-                'nationality': customer_info_nationality,
-                'residCountry': customer_info_resid_country,
-                'residCity': customer_info_resid_city,
-                'residZipCode': customer_info_resid_zip_code,
-                'residStreet': customer_info_resid_street,
-                'fullName': customer_info_full_name,
-                'email': customer_info_email
-            },
-            'duration': duration
-        }
-
-        if customer_info_resid_state:
-            payload['customerInfo']['residState'] = customer_info_resid_state
-
-        if customer_info_resid_building_no:
-            payload['customerInfo']['residBuildingNo'] = customer_info_resid_building_no
-
-        return await self.post(endpoint, data=payload)
-
-    async def get_invoices(self, id=None, start=None, end=None, limit=10):
-        """
-        List submitted invoices
-
-        # Attributes
-        @param id str: Unique invoice identifier
-        @param start int: Millisecond start time
-        @param end int: Millisecond end time
-        @param limit int: Millisecond start time
-        """
-        endpoint = 'auth/r/ext/pay/invoices'
-        payload = {}
-
-        if id:
-            payload['id'] = id
-
-        if start:
-            payload['start'] = start
-
-        if end:
-            payload['end'] = end
-
-        if limit:
-            payload['limit'] = limit
-
-        return await self.post(endpoint, data=payload)
-
-    async def complete_invoice(self, id, pay_ccy, deposit_id=None, ledger_id=None):
-        """
-        Manually complete an invoice
-
-        # Attributes
-        @param id str: Unique invoice identifier
-        @param pay_ccy str: Paid invoice currency, should be one of values under payCurrencies field on invoice
-        @param deposit_id int: Movement/Deposit Id linked to invoice as payment
-        @param ledger_id int: Ledger entry Id linked to invoice as payment, use either depositId or ledgerId
-        """
-        endpoint = 'auth/w/ext/pay/invoice/complete'
-        payload = {
-            'id': id,
-            'payCcy': pay_ccy
-        }
-
-        if deposit_id:
-            payload['depositId'] = deposit_id
-
-        if ledger_id:
-            payload['ledgerId'] = ledger_id
-
-        return await self.post(endpoint, data=payload)
-
-    async def get_unlinked_deposits(self, ccy, start=None, end=None):
-        """
-        Retrieve deposits that possibly could be linked to bitfinex pay invoices
-
-        # Attributes
-        @param ccy str: Pay currency to search deposits for, supported values are: BTC, ETH, UST-ETH, UST-TRX, UST-LBT, LNX, LBT
-        @param start int: Millisecond start time
-        @param end int: Millisecond end time
-        """
-        endpoint = 'auth/r/ext/pay/deposits/unlinked'
-        payload = {
-            'ccy': ccy
-        }
-
-        if start:
-            payload['start'] = start
-
-        if end:
-            payload['end'] = end
-
-        return await self.post(endpoint, data=payload)
diff --git a/bfxapi/rest/endpoints/__init__.py b/bfxapi/rest/endpoints/__init__.py
new file mode 100644
index 00000000..2775e2eb
--- /dev/null
+++ b/bfxapi/rest/endpoints/__init__.py
@@ -0,0 +1,5 @@
+from .bfx_rest_interface import BfxRestInterface
+
+from .rest_public_endpoints import RestPublicEndpoints
+from .rest_authenticated_endpoints import RestAuthenticatedEndpoints
+from .rest_merchant_endpoints import RestMerchantEndpoints
diff --git a/bfxapi/rest/endpoints/bfx_rest_interface.py b/bfxapi/rest/endpoints/bfx_rest_interface.py
new file mode 100644
index 00000000..12a06f43
--- /dev/null
+++ b/bfxapi/rest/endpoints/bfx_rest_interface.py
@@ -0,0 +1,13 @@
+from .rest_public_endpoints import RestPublicEndpoints
+from .rest_authenticated_endpoints import RestAuthenticatedEndpoints
+from .rest_merchant_endpoints import RestMerchantEndpoints
+
+class BfxRestInterface:
+    VERSION = 2
+
+    def __init__(self, host, credentials = None):
+        api_key, api_secret = (credentials['api_key'], credentials['api_secret']) if credentials else (None, None)
+
+        self.public = RestPublicEndpoints(host=host)
+        self.auth = RestAuthenticatedEndpoints(host=host, api_key=api_key, api_secret=api_secret)
+        self.merchant = RestMerchantEndpoints(host=host, api_key=api_key, api_secret=api_secret)
diff --git a/bfxapi/rest/endpoints/rest_authenticated_endpoints.py b/bfxapi/rest/endpoints/rest_authenticated_endpoints.py
new file mode 100644
index 00000000..8a2e61ec
--- /dev/null
+++ b/bfxapi/rest/endpoints/rest_authenticated_endpoints.py
@@ -0,0 +1,474 @@
+from typing import Dict, List, Tuple, Union, Literal, Optional
+from decimal import Decimal
+from datetime import datetime
+
+from .. types import Notification, \
+    UserInfo, LoginHistory, BalanceAvailable, \
+    Order, Position, Trade, \
+    FundingTrade, OrderTrade, Ledger, \
+    FundingOffer, FundingCredit, FundingLoan, \
+    FundingAutoRenew, FundingInfo, Wallet, \
+    Transfer, Withdrawal, DepositAddress, \
+    LightningNetworkInvoice, Movement, SymbolMarginInfo, \
+    BaseMarginInfo, PositionClaim, PositionIncreaseInfo, \
+    PositionIncrease, PositionHistory, PositionSnapshot, \
+    PositionAudit, DerivativePositionCollateral, DerivativePositionCollateralLimits
+
+from .. import serializers
+
+from .. serializers import _Notification
+from .. enums import Sort, OrderType, FundingOfferType
+from .. middleware import Middleware
+
+from ...utils.json_encoder import JSON
+
+class RestAuthenticatedEndpoints(Middleware):
+    def get_user_info(self) -> UserInfo:
+        return serializers.UserInfo \
+            .parse(*self._post("auth/r/info/user"))
+
+    def get_login_history(self) -> List[LoginHistory]:
+        return [ serializers.LoginHistory.parse(*sub_data)
+            for sub_data in self._post("auth/r/logins/hist") ]
+
+    def get_balance_available_for_orders_or_offers(self,
+                                                   symbol: str,
+                                                   type: str,
+                                                   *,
+                                                   dir: Optional[int] = None,
+                                                   rate: Optional[str] = None,
+                                                   lev: Optional[str] = None) -> BalanceAvailable:
+        body = {
+            "symbol": symbol, "type": type, "dir": dir,
+            "rate": rate, "lev": lev
+        }
+
+        return serializers.BalanceAvailable \
+            .parse(*self._post("auth/calc/order/avail", body=body))
+
+    def get_wallets(self) -> List[Wallet]:
+        return [ serializers.Wallet.parse(*sub_data) \
+            for sub_data in self._post("auth/r/wallets") ]
+
+    def get_orders(self,
+                   *,
+                   symbol: Optional[str] = None,
+                   ids: Optional[List[str]] = None) -> List[Order]:
+        if symbol is None:
+            endpoint = "auth/r/orders"
+        else: endpoint = f"auth/r/orders/{symbol}"
+
+        return [ serializers.Order.parse(*sub_data) \
+            for sub_data in self._post(endpoint, body={ "id": ids }) ]
+
+    def submit_order(self,
+                     type: OrderType,
+                     symbol: str,
+                     amount: Union[Decimal, float, str],
+                     *,
+                     price: Optional[Union[Decimal, float, str]] = None,
+                     lev: Optional[int] = None,
+                     price_trailing: Optional[Union[Decimal, float, str]] = None,
+                     price_aux_limit: Optional[Union[Decimal, float, str]] = None,
+                     price_oco_stop: Optional[Union[Decimal, float, str]] = None,
+                     gid: Optional[int] = None,
+                     cid: Optional[int] = None,
+                     flags: Optional[int] = 0,
+                     tif: Optional[Union[datetime, str]] = None,
+                     meta: Optional[JSON] = None) -> Notification[Order]:
+        body = {
+            "type": type, "symbol": symbol, "amount": amount,
+            "price": price, "lev": lev, "price_trailing": price_trailing,
+            "price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop, "gid": gid,
+            "cid": cid, "flags": flags, "tif": tif,
+            "meta": meta
+        }
+
+        return _Notification[Order](serializers.Order) \
+            .parse(*self._post("auth/w/order/submit", body=body))
+
+    def update_order(self,
+                     id: int,
+                     *,
+                     amount: Optional[Union[Decimal, float, str]] = None,
+                     price: Optional[Union[Decimal, float, str]] = None,
+                     cid: Optional[int] = None,
+                     cid_date: Optional[str] = None,
+                     gid: Optional[int] = None,
+                     flags: Optional[int] = 0,
+                     lev: Optional[int] = None,
+                     delta: Optional[Union[Decimal, float, str]] = None,
+                     price_aux_limit: Optional[Union[Decimal, float, str]] = None,
+                     price_trailing: Optional[Union[Decimal, float, str]] = None,
+                     tif: Optional[Union[datetime, str]] = None) -> Notification[Order]:
+        body = {
+            "id": id, "amount": amount, "price": price,
+            "cid": cid, "cid_date": cid_date, "gid": gid,
+            "flags": flags, "lev": lev, "delta": delta,
+            "price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif
+        }
+
+        return _Notification[Order](serializers.Order) \
+            .parse(*self._post("auth/w/order/update", body=body))
+
+    def cancel_order(self,
+                     *,
+                     id: Optional[int] = None,
+                     cid: Optional[int] = None,
+                     cid_date: Optional[str] = None) -> Notification[Order]:
+        return _Notification[Order](serializers.Order) \
+            .parse(*self._post("auth/w/order/cancel", \
+                body={ "id": id, "cid": cid, "cid_date": cid_date }))
+
+    def cancel_order_multi(self,
+                           *,
+                           ids: Optional[List[int]] = None,
+                           cids: Optional[List[Tuple[int, str]]] = None,
+                           gids: Optional[List[int]] = None,
+                           all: bool = False) -> Notification[List[Order]]:
+        body = {
+            "ids": ids, "cids": cids, "gids": gids, 
+            "all": int(all)
+        }
+
+        return _Notification[List[Order]](serializers.Order, is_iterable=True) \
+            .parse(*self._post("auth/w/order/cancel/multi", body=body))
+
+    def get_orders_history(self,
+                           *,
+                           symbol: Optional[str] = None,
+                           ids: Optional[List[int]] = None,
+                           start: Optional[str] = None,
+                           end: Optional[str] = None,
+                           limit: Optional[int] = None) -> List[Order]:
+        if symbol is None:
+            endpoint = "auth/r/orders/hist"
+        else: endpoint = f"auth/r/orders/{symbol}/hist"
+
+        body = {
+            "id": ids, "start": start, "end": end, 
+            "limit": limit 
+        }
+
+        return [ serializers.Order.parse(*sub_data) \
+            for sub_data in self._post(endpoint, body=body) ]
+
+    def get_order_trades(self,
+                         symbol: str,
+                         id: int) -> List[OrderTrade]:
+        return [ serializers.OrderTrade.parse(*sub_data) \
+            for sub_data in self._post(f"auth/r/order/{symbol}:{id}/trades") ]
+
+    def get_trades_history(self,
+                           *,
+                           symbol: Optional[str] = None,
+                           sort: Optional[Sort] = None,
+                           start: Optional[str] = None,
+                           end: Optional[str] = None,
+                           limit: Optional[int] = None) -> List[Trade]:
+        if symbol is None:
+            endpoint = "auth/r/trades/hist"
+        else: endpoint = f"auth/r/trades/{symbol}/hist"
+
+        body = {
+            "sort": sort, "start": start, "end": end,
+            "limit": limit
+        }
+
+        return [ serializers.Trade.parse(*sub_data) \
+            for sub_data in self._post(endpoint, body=body) ]
+
+    def get_ledgers(self,
+                    currency: str,
+                    *,
+                    category: Optional[int] = None,
+                    start: Optional[str] = None,
+                    end: Optional[str] = None,
+                    limit: Optional[int] = None) -> List[Ledger]:
+        body = {
+            "category": category, "start": start, "end": end, 
+            "limit": limit 
+        }
+
+        return [ serializers.Ledger.parse(*sub_data) \
+            for sub_data in self._post(f"auth/r/ledgers/{currency}/hist", body=body) ]
+
+    def get_base_margin_info(self) -> BaseMarginInfo:
+        return serializers.BaseMarginInfo \
+            .parse(*(self._post("auth/r/info/margin/base")[1]))
+
+    def get_symbol_margin_info(self, symbol: str) -> SymbolMarginInfo:
+        return serializers.SymbolMarginInfo \
+            .parse(*self._post(f"auth/r/info/margin/{symbol}"))
+
+    def get_all_symbols_margin_info(self) -> List[SymbolMarginInfo]:
+        return [ serializers.SymbolMarginInfo.parse(*sub_data) \
+            for sub_data in self._post("auth/r/info/margin/sym_all") ]
+
+    def get_positions(self) -> List[Position]:
+        return [ serializers.Position.parse(*sub_data) \
+            for sub_data in self._post("auth/r/positions") ]
+
+    def claim_position(self,
+                       id: int,
+                       *,
+                       amount: Optional[Union[Decimal, float, str]] = None) -> Notification[PositionClaim]:
+        return _Notification[PositionClaim](serializers.PositionClaim) \
+            .parse(*self._post("auth/w/position/claim", \
+                body={ "id": id, "amount": amount }))
+
+    def increase_position(self,
+                          symbol: str,
+                          amount: Union[Decimal, float, str]) -> Notification[PositionIncrease]:
+        return _Notification[PositionIncrease](serializers.PositionIncrease) \
+            .parse(*self._post("auth/w/position/increase", \
+                body={ "symbol": symbol, "amount": amount }))
+
+    def get_increase_position_info(self,
+                                   symbol: str,
+                                   amount: Union[Decimal, float, str]) -> PositionIncreaseInfo:
+        return serializers.PositionIncreaseInfo \
+            .parse(*self._post("auth/r/position/increase/info", \
+                body={ "symbol": symbol, "amount": amount }))
+
+    def get_positions_history(self,
+                              *,
+                              start: Optional[str] = None,
+                              end: Optional[str] = None,
+                              limit: Optional[int] = None) -> List[PositionHistory]:
+        return [ serializers.PositionHistory.parse(*sub_data) \
+            for sub_data in self._post("auth/r/positions/hist", \
+                body={ "start": start, "end": end, "limit": limit }) ]
+
+    def get_positions_snapshot(self,
+                               *,
+                               start: Optional[str] = None,
+                               end: Optional[str] = None,
+                               limit: Optional[int] = None) -> List[PositionSnapshot]:
+        return [ serializers.PositionSnapshot.parse(*sub_data) \
+            for sub_data in self._post("auth/r/positions/snap", \
+                body={ "start": start, "end": end, "limit": limit }) ]
+
+    def get_positions_audit(self,
+                            *,
+                            ids: Optional[List[int]] = None,
+                            start: Optional[str] = None,
+                            end: Optional[str] = None,
+                            limit: Optional[int] = None) -> List[PositionAudit]:
+        body = {
+            "ids": ids, "start": start, "end": end, 
+            "limit": limit 
+        }
+
+        return [ serializers.PositionAudit.parse(*sub_data) \
+            for sub_data in self._post("auth/r/positions/audit", body=body) ]
+
+    def set_derivative_position_collateral(self,
+                                           symbol: str,
+                                           collateral: Union[Decimal, float, str]) -> DerivativePositionCollateral:
+        return serializers.DerivativePositionCollateral \
+            .parse(*(self._post("auth/w/deriv/collateral/set", \
+                body={ "symbol": symbol, "collateral": collateral })[0]))
+
+    def get_derivative_position_collateral_limits(self, symbol: str) -> DerivativePositionCollateralLimits:
+        return serializers.DerivativePositionCollateralLimits \
+            .parse(*self._post("auth/calc/deriv/collateral/limit", body={ "symbol": symbol }))
+
+    def get_funding_offers(self, *, symbol: Optional[str] = None) -> List[FundingOffer]:
+        if symbol is None:
+            endpoint = "auth/r/funding/offers"
+        else: endpoint = f"auth/r/funding/offers/{symbol}"
+
+        return [ serializers.FundingOffer.parse(*sub_data) \
+            for sub_data in self._post(endpoint) ]
+
+    #pylint: disable-next=too-many-arguments
+    def submit_funding_offer(self,
+                             type: FundingOfferType,
+                             symbol: str,
+                             amount: Union[Decimal, float, str],
+                             rate: Union[Decimal, float, str],
+                             period: int,
+                             *,
+                             flags: Optional[int] = 0) -> Notification[FundingOffer]:
+        body = {
+            "type": type, "symbol": symbol, "amount": amount,
+            "rate": rate, "period": period, "flags": flags
+        }
+
+        return _Notification[FundingOffer](serializers.FundingOffer) \
+            .parse(*self._post("auth/w/funding/offer/submit", body=body))
+
+    def cancel_funding_offer(self, id: int) -> Notification[FundingOffer]:
+        return _Notification[FundingOffer](serializers.FundingOffer) \
+            .parse(*self._post("auth/w/funding/offer/cancel", body={ "id": id }))
+
+    def cancel_all_funding_offers(self, currency: str) -> Notification[Literal[None]]:
+        return _Notification[Literal[None]](None) \
+            .parse(*self._post("auth/w/funding/offer/cancel/all", body={ "currency": currency }))
+
+    def submit_funding_close(self, id: int) -> Notification[Literal[None]]:
+        return _Notification[Literal[None]](None) \
+            .parse(*self._post("auth/w/funding/close", body={ "id": id }))
+
+    def toggle_auto_renew(self,
+                          status: bool,
+                          currency: str,
+                          *,
+                          amount: Optional[str] = None,
+                          rate: Optional[int] = None,
+                          period: Optional[int] = None) -> Notification[FundingAutoRenew]:
+        body = {
+            "status": int(status), "currency": currency, "amount": amount,
+            "rate": rate, "period": period
+        }
+
+        return _Notification[FundingAutoRenew](serializers.FundingAutoRenew) \
+            .parse(*self._post("auth/w/funding/auto", body=body))
+
+    def toggle_keep_funding(self,
+                            type: Literal["credit", "loan"],
+                            *,
+                            ids: Optional[List[int]] = None,
+                            changes: Optional[Dict[int, Literal[1, 2]]] = None) -> Notification[Literal[None]]:
+        return _Notification[Literal[None]](None) \
+            .parse(*self._post("auth/w/funding/keep", \
+                body={ "type": type, "id": ids, "changes": changes }))
+
+    def get_funding_offers_history(self,
+                                   *,
+                                   symbol: Optional[str] = None,
+                                   start: Optional[str] = None,
+                                   end: Optional[str] = None,
+                                   limit: Optional[int] = None) -> List[FundingOffer]:
+        if symbol is None:
+            endpoint = "auth/r/funding/offers/hist"
+        else: endpoint = f"auth/r/funding/offers/{symbol}/hist"
+
+        return [ serializers.FundingOffer.parse(*sub_data) \
+            for sub_data in self._post(endpoint, \
+                body={ "start": start, "end": end, "limit": limit }) ]
+
+    def get_funding_loans(self, *, symbol: Optional[str] = None) -> List[FundingLoan]:
+        if symbol is None:
+            endpoint = "auth/r/funding/loans"
+        else: endpoint = f"auth/r/funding/loans/{symbol}"
+
+        return [ serializers.FundingLoan.parse(*sub_data) \
+            for sub_data in self._post(endpoint) ]
+
+    def get_funding_loans_history(self,
+                                  *,
+                                  symbol: Optional[str] = None,
+                                  start: Optional[str] = None,
+                                  end: Optional[str] = None,
+                                  limit: Optional[int] = None) -> List[FundingLoan]:
+        if symbol is None:
+            endpoint = "auth/r/funding/loans/hist"
+        else: endpoint = f"auth/r/funding/loans/{symbol}/hist"
+
+        return [ serializers.FundingLoan.parse(*sub_data) \
+            for sub_data in self._post(endpoint, \
+                body={ "start": start, "end": end, "limit": limit }) ]
+
+    def get_funding_credits(self, *, symbol: Optional[str] = None) -> List[FundingCredit]:
+        if symbol is None:
+            endpoint = "auth/r/funding/credits"
+        else: endpoint = f"auth/r/funding/credits/{symbol}"
+
+        return [ serializers.FundingCredit.parse(*sub_data) \
+            for sub_data in self._post(endpoint) ]
+
+    def get_funding_credits_history(self,
+                                    *,
+                                    symbol: Optional[str] = None,
+                                    start: Optional[str] = None,
+                                    end: Optional[str] = None,
+                                    limit: Optional[int] = None) -> List[FundingCredit]:
+        if symbol is None:
+            endpoint = "auth/r/funding/credits/hist"
+        else: endpoint = f"auth/r/funding/credits/{symbol}/hist"
+
+        return [ serializers.FundingCredit.parse(*sub_data) \
+            for sub_data in self._post(endpoint, \
+                body={ "start": start, "end": end, "limit": limit }) ]
+
+    def get_funding_trades_history(self,
+                                   *,
+                                   symbol: Optional[str] = None,
+                                   sort: Optional[Sort] = None,
+                                   start: Optional[str] = None,
+                                   end: Optional[str] = None,
+                                   limit: Optional[int] = None) -> List[FundingTrade]:
+        if symbol is None:
+            endpoint = "auth/r/funding/trades/hist"
+        else: endpoint = f"auth/r/funding/trades/{symbol}/hist"
+
+        body = {
+            "sort": sort, "start": start, "end": end, 
+            "limit": limit }
+
+        return [ serializers.FundingTrade.parse(*sub_data) \
+            for sub_data in self._post(endpoint, body=body) ]
+
+    def get_funding_info(self, key: str) -> FundingInfo:
+        return serializers.FundingInfo \
+            .parse(*(self._post(f"auth/r/info/funding/{key}")[2]))
+
+    #pylint: disable-next=too-many-arguments
+    def transfer_between_wallets(self,
+                                 from_wallet: str,
+                                 to_wallet: str,
+                                 currency: str,
+                                 currency_to: str,
+                                 amount: Union[Decimal, float, str]) -> Notification[Transfer]:
+        body = {
+            "from": from_wallet, "to": to_wallet, "currency": currency, 
+            "currency_to": currency_to, "amount": amount
+        }
+
+        return _Notification[Transfer](serializers.Transfer) \
+            .parse(*self._post("auth/w/transfer", body=body))
+
+    def submit_wallet_withdrawal(self,
+                                 wallet: str,
+                                 method: str,
+                                 address: str,
+                                 amount: Union[Decimal, float, str]) -> Notification[Withdrawal]:
+        body = {
+            "wallet": wallet, "method": method, "address": address, 
+            "amount": amount
+        }
+
+        return _Notification[Withdrawal](serializers.Withdrawal) \
+            .parse(*self._post("auth/w/withdraw", body=body))
+
+    def get_deposit_address(self,
+                            wallet: str,
+                            method: str,
+                            renew: bool = False) -> Notification[DepositAddress]:
+        return _Notification[DepositAddress](serializers.DepositAddress) \
+            .parse(*self._post("auth/w/deposit/address", \
+                body={ "wallet": wallet, "method": method, "renew": int(renew) }))
+
+    def generate_deposit_invoice(self,
+                                 wallet: str,
+                                 currency: str,
+                                 amount: Union[Decimal, float, str]) -> LightningNetworkInvoice:
+        return serializers.LightningNetworkInvoice \
+            .parse(*self._post("auth/w/deposit/invoice", \
+                body={ "wallet": wallet, "currency": currency, "amount": amount }))
+
+    def get_movements(self,
+                      *,
+                      currency: Optional[str] = None,
+                      start: Optional[str] = None,
+                      end: Optional[str] = None,
+                      limit: Optional[int] = None) -> List[Movement]:
+        if currency is None:
+            endpoint = "auth/r/movements/hist"
+        else: endpoint = f"auth/r/movements/{currency}/hist"
+
+        return [ serializers.Movement.parse(*sub_data) \
+            for sub_data in self._post(endpoint, \
+                body={ "start": start, "end": end, "limit": limit }) ]
diff --git a/bfxapi/rest/endpoints/rest_merchant_endpoints.py b/bfxapi/rest/endpoints/rest_merchant_endpoints.py
new file mode 100644
index 00000000..bf806af9
--- /dev/null
+++ b/bfxapi/rest/endpoints/rest_merchant_endpoints.py
@@ -0,0 +1,160 @@
+from typing import TypedDict, Dict, List, Union, Literal, Optional, Any
+
+from decimal import Decimal
+
+from .. types import \
+    InvoiceSubmission, InvoicePage, InvoiceStats, \
+    CurrencyConversion, MerchantDeposit, MerchantUnlinkedDeposit
+
+from .. enums import MerchantSettingsKey
+
+from .. middleware import Middleware
+
+from ...utils.camel_and_snake_case_helpers import to_snake_case_keys, to_camel_case_keys
+
+_CustomerInfo = TypedDict("_CustomerInfo", {
+    "nationality": str, "resid_country": str, "resid_city": str, 
+    "resid_zip_code": str, "resid_street": str, "resid_building_no": str, 
+    "full_name": str, "email": str, "tos_accepted": bool
+})
+
+class RestMerchantEndpoints(Middleware):
+    #pylint: disable-next=too-many-arguments
+    def submit_invoice(self,
+                       amount: Union[Decimal, float, str],
+                       currency: str,
+                       order_id: str,
+                       customer_info: _CustomerInfo,
+                       pay_currencies: List[str],
+                       *,
+                       duration: Optional[int] = None,
+                       webhook: Optional[str] = None,
+                       redirect_url: Optional[str] = None) -> InvoiceSubmission:
+        body = to_camel_case_keys({
+            "amount": amount, "currency": currency, "order_id": order_id,
+            "customer_info": customer_info, "pay_currencies": pay_currencies, "duration": duration,
+            "webhook": webhook, "redirect_url": redirect_url
+        })
+
+        data = to_snake_case_keys(self._post("auth/w/ext/pay/invoice/create", body=body))
+
+        return InvoiceSubmission.parse(data)
+
+    def get_invoices(self,
+                     *,
+                     id: Optional[str] = None,
+                     start: Optional[str] = None,
+                     end: Optional[str] = None,
+                     limit: Optional[int] = None) -> List[InvoiceSubmission]:
+        body = {
+            "id": id, "start": start, "end": end, 
+            "limit": limit
+        }
+
+        response = self._post("auth/r/ext/pay/invoices", body=body)
+
+        return [ InvoiceSubmission.parse(sub_data) for sub_data in to_snake_case_keys(response) ]
+
+    def get_invoices_paginated(self,
+                               page: int = 1,
+                               page_size: int = 10,
+                               sort: Literal["asc", "desc"] = "asc",
+                               sort_field: Literal["t", "amount", "status"] = "t",
+                               *,
+                               status: Optional[List[Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"]]] = None,
+                               fiat: Optional[List[str]] = None,
+                               crypto: Optional[List[str]] = None,
+                               id: Optional[str] = None,
+                               order_id: Optional[str] = None) -> InvoicePage:
+        body = to_camel_case_keys({
+            "page": page, "page_size": page_size, "sort": sort,
+            "sort_field": sort_field, "status": status, "fiat": fiat,
+            "crypto": crypto, "id": id, "order_id": order_id
+        })
+
+        data = to_snake_case_keys(self._post("auth/r/ext/pay/invoices/paginated", body=body))
+
+        return InvoicePage.parse(data)
+
+    def get_invoice_count_stats(self,
+                                status: Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"],
+                                format: str) -> List[InvoiceStats]:
+        return [ InvoiceStats(**sub_data) for sub_data in \
+            self._post("auth/r/ext/pay/invoice/stats/count", body={ "status": status, "format": format }) ]
+
+    def get_invoice_earning_stats(self,
+                                  currency: str,
+                                  format: str) -> List[InvoiceStats]:
+        return [ InvoiceStats(**sub_data) for sub_data in \
+            self._post("auth/r/ext/pay/invoice/stats/earning", body={ "currency": currency, "format": format }) ]
+
+    def complete_invoice(self,
+                         id: str,
+                         pay_currency: str,
+                         *,
+                         deposit_id: Optional[int] = None,
+                         ledger_id: Optional[int] = None) -> InvoiceSubmission:
+        return InvoiceSubmission.parse(to_snake_case_keys(self._post("auth/w/ext/pay/invoice/complete", body={
+            "id": id, "payCcy": pay_currency, "depositId": deposit_id, 
+            "ledgerId": ledger_id
+        })))
+
+    def expire_invoice(self, id: str) -> InvoiceSubmission:
+        body = { "id": id }
+        response = self._post("auth/w/ext/pay/invoice/expire", body=body)
+        return InvoiceSubmission.parse(to_snake_case_keys(response))
+
+    def get_currency_conversion_list(self) -> List[CurrencyConversion]:
+        return [
+            CurrencyConversion(
+                base_currency=sub_data["baseCcy"],
+                convert_currency=sub_data["convertCcy"],
+                created=sub_data["created"]
+            ) for sub_data in self._post("auth/r/ext/pay/settings/convert/list")
+        ]
+
+    def add_currency_conversion(self,
+                                base_currency: str,
+                                convert_currency: str) -> bool:
+        return bool(self._post("auth/w/ext/pay/settings/convert/create", body={
+            "baseCcy": base_currency,
+            "convertCcy": convert_currency
+        }))
+
+    def remove_currency_conversion(self,
+                                   base_currency: str,
+                                   convert_currency: str) -> bool:
+        return bool(self._post("auth/w/ext/pay/settings/convert/remove", body={
+            "baseCcy": base_currency,
+            "convertCcy": convert_currency
+        }))
+
+    def set_merchant_settings(self,
+                              key: MerchantSettingsKey,
+                              val: Any) -> bool:
+        return bool(self._post("auth/w/ext/pay/settings/set", body={ "key": key, "val": val }))
+
+    def get_merchant_settings(self, key: MerchantSettingsKey) -> Any:
+        return self._post("auth/r/ext/pay/settings/get", body={ "key": key })
+
+    def list_merchant_settings(self, keys: List[MerchantSettingsKey] = []) -> Dict[MerchantSettingsKey, Any]:
+        return self._post("auth/r/ext/pay/settings/list", body={ "keys": keys })
+
+    def get_deposits(self,
+                     start: int,
+                     end: int,
+                     *,
+                     ccy: Optional[str] = None,
+                     unlinked: Optional[bool] = None) -> List[MerchantDeposit]:
+        body = { "from": start, "to": end, "ccy": ccy, "unlinked": unlinked }
+        response = self._post("auth/r/ext/pay/deposits", body=body)
+        return [ MerchantDeposit(**sub_data) for sub_data in to_snake_case_keys(response) ]
+
+    def get_unlinked_deposits(self,
+                              ccy: str,
+                              *,
+                              start: Optional[int] = None,
+                              end: Optional[int] = None) -> List[MerchantUnlinkedDeposit]:
+        body = { "ccy": ccy, "start": start, "end": end }
+        response = self._post("/auth/r/ext/pay/deposits/unlinked", body=body)
+        return [ MerchantUnlinkedDeposit(**sub_data) for sub_data in to_snake_case_keys(response) ]
diff --git a/bfxapi/rest/endpoints/rest_public_endpoints.py b/bfxapi/rest/endpoints/rest_public_endpoints.py
new file mode 100644
index 00000000..c80d86b0
--- /dev/null
+++ b/bfxapi/rest/endpoints/rest_public_endpoints.py
@@ -0,0 +1,280 @@
+from typing import List, Union, Literal, Optional, Any, cast
+
+from decimal import Decimal
+
+from .. types import \
+    PlatformStatus, TradingPairTicker, FundingCurrencyTicker, \
+    TickersHistory, TradingPairTrade, FundingCurrencyTrade, \
+    TradingPairBook, FundingCurrencyBook, TradingPairRawBook, \
+    FundingCurrencyRawBook, Statistic, Candle, \
+    DerivativesStatus, Liquidation, Leaderboard, \
+    FundingStatistic, PulseProfile, PulseMessage, \
+    TradingMarketAveragePrice, FundingMarketAveragePrice, FxRate
+
+from .. import serializers
+from .. enums import Config, Sort
+from .. middleware import Middleware
+
+class RestPublicEndpoints(Middleware):
+    def conf(self, config: Config) -> Any:
+        return self._get(f"conf/{config}")[0]
+
+    def get_platform_status(self) -> PlatformStatus:
+        return serializers.PlatformStatus.parse(*self._get("platform/status"))
+
+    def get_tickers(self, symbols: List[str]) -> List[Union[TradingPairTicker, FundingCurrencyTicker]]:
+        data = self._get("tickers", params={ "symbols": ",".join(symbols) })
+
+        parsers = { "t": serializers.TradingPairTicker.parse, "f": serializers.FundingCurrencyTicker.parse }
+
+        return [ cast(Union[TradingPairTicker, FundingCurrencyTicker], \
+            parsers[sub_data[0][0]](*sub_data)) for sub_data in data ]
+
+    def get_t_tickers(self, pairs: Union[List[str], Literal["ALL"]]) -> List[TradingPairTicker]:
+        if isinstance(pairs, str) and pairs == "ALL":
+            return [ cast(TradingPairTicker, sub_data) for sub_data in self.get_tickers([ "ALL" ]) \
+                if cast(str, sub_data.symbol).startswith("t") ]
+
+        data = self.get_tickers(list(pairs))
+
+        return cast(List[TradingPairTicker], data)
+
+    def get_f_tickers(self, currencies: Union[List[str], Literal["ALL"]]) -> List[FundingCurrencyTicker]:
+        if isinstance(currencies, str) and currencies == "ALL":
+            return [ cast(FundingCurrencyTicker, sub_data) for sub_data in self.get_tickers([ "ALL" ]) \
+                if cast(str, sub_data.symbol).startswith("f") ]
+
+        data = self.get_tickers(list(currencies))
+
+        return cast(List[FundingCurrencyTicker], data)
+
+    def get_t_ticker(self, pair: str) -> TradingPairTicker:
+        return serializers.TradingPairTicker.parse(*([pair] + self._get(f"ticker/{pair}")))
+
+    def get_f_ticker(self, currency: str) -> FundingCurrencyTicker:
+        return serializers.FundingCurrencyTicker.parse(*([currency] + self._get(f"ticker/{currency}")))
+
+    def get_tickers_history(self,
+                            symbols: List[str],
+                            *,
+                            start: Optional[str] = None,
+                            end: Optional[str] = None,
+                            limit: Optional[int] = None) -> List[TickersHistory]:
+        return [ serializers.TickersHistory.parse(*sub_data) for sub_data in self._get("tickers/hist", params={
+            "symbols": ",".join(symbols),
+            "start": start, "end": end,
+            "limit": limit
+        }) ]
+
+    def get_t_trades(self,
+                     pair: str,
+                     *,
+                     limit: Optional[int] = None,
+                     start: Optional[str] = None,
+                     end: Optional[str] = None,
+                     sort: Optional[Sort] = None) -> List[TradingPairTrade]:
+        params = { "limit": limit, "start": start, "end": end, "sort": sort }
+        data = self._get(f"trades/{pair}/hist", params=params)
+        return [ serializers.TradingPairTrade.parse(*sub_data) for sub_data in data ]
+
+    def get_f_trades(self,
+                     currency: str,
+                     *,
+                     limit: Optional[int] = None,
+                     start: Optional[str] = None,
+                     end: Optional[str] = None,
+                     sort: Optional[Sort] = None) -> List[FundingCurrencyTrade]:
+        params = { "limit": limit, "start": start, "end": end, "sort": sort }
+        data = self._get(f"trades/{currency}/hist", params=params)
+        return [ serializers.FundingCurrencyTrade.parse(*sub_data) for sub_data in data ]
+
+    def get_t_book(self,
+                   pair: str,
+                   precision: Literal["P0", "P1", "P2", "P3", "P4"],
+                   *,
+                   len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairBook]:
+        return [ serializers.TradingPairBook.parse(*sub_data) \
+            for sub_data in self._get(f"book/{pair}/{precision}", params={ "len": len }) ]
+
+    def get_f_book(self,
+                   currency: str,
+                   precision: Literal["P0", "P1", "P2", "P3", "P4"],
+                   *,
+                   len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyBook]:
+        return [ serializers.FundingCurrencyBook.parse(*sub_data) \
+            for sub_data in self._get(f"book/{currency}/{precision}", params={ "len": len }) ]
+
+    def get_t_raw_book(self,
+                       pair: str,
+                       *,
+                       len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairRawBook]:
+        return [ serializers.TradingPairRawBook.parse(*sub_data) \
+            for sub_data in self._get(f"book/{pair}/R0", params={ "len": len }) ]
+
+    def get_f_raw_book(self,
+                       currency: str,
+                       *,
+                       len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyRawBook]:
+        return [ serializers.FundingCurrencyRawBook.parse(*sub_data) \
+            for sub_data in self._get(f"book/{currency}/R0", params={ "len": len }) ]
+
+    def get_stats_hist(self,
+                       resource: str,
+                       *,
+                       sort: Optional[Sort] = None,
+                       start: Optional[str] = None,
+                       end: Optional[str] = None,
+                       limit: Optional[int] = None) -> List[Statistic]:
+        params = { "sort": sort, "start": start, "end": end, "limit": limit }
+        data = self._get(f"stats1/{resource}/hist", params=params)
+        return [ serializers.Statistic.parse(*sub_data) for sub_data in data ]
+
+    def get_stats_last(self,
+                       resource: str,
+                       *,
+                       sort: Optional[Sort] = None,
+                       start: Optional[str] = None,
+                       end: Optional[str] = None,
+                       limit: Optional[int] = None) -> Statistic:
+        params = { "sort": sort, "start": start, "end": end, "limit": limit }
+        data = self._get(f"stats1/{resource}/last", params=params)
+        return serializers.Statistic.parse(*data)
+
+    def get_candles_hist(self,
+                         symbol: str,
+                         tf: str = "1m",
+                         *,
+                         sort: Optional[Sort] = None,
+                         start: Optional[str] = None,
+                         end: Optional[str] = None,
+                         limit: Optional[int] = None) -> List[Candle]:
+        params = { "sort": sort, "start": start, "end": end, "limit": limit }
+        data = self._get(f"candles/trade:{tf}:{symbol}/hist", params=params)
+        return [ serializers.Candle.parse(*sub_data) for sub_data in data ]
+
+    def get_candles_last(self,
+                         symbol: str,
+                         tf: str = "1m",
+                         *,
+                         sort: Optional[Sort] = None,
+                         start: Optional[str] = None,
+                         end: Optional[str] = None,
+                         limit: Optional[int] = None) -> Candle:
+        params = { "sort": sort, "start": start, "end": end, "limit": limit }
+        data = self._get(f"candles/trade:{tf}:{symbol}/last", params=params)
+        return serializers.Candle.parse(*data)
+
+    def get_derivatives_status(self, keys: Union[List[str], Literal["ALL"]]) -> List[DerivativesStatus]:
+        if keys == "ALL":
+            params = { "keys": "ALL" }
+        else:  params = { "keys": ",".join(keys) }
+
+        data = self._get("status/deriv", params=params)
+
+        return [ serializers.DerivativesStatus.parse(*sub_data) for sub_data in data ]
+
+    def get_derivatives_status_history(self,
+                                       type: str,
+                                       symbol: str,
+                                       *,
+                                       sort: Optional[Sort] = None,
+                                       start: Optional[str] = None,
+                                       end: Optional[str] = None,
+                                       limit: Optional[int] = None) -> List[DerivativesStatus]:
+        params = { "sort": sort, "start": start, "end": end, "limit": limit }
+        data = self._get(f"status/{type}/{symbol}/hist", params=params)
+        return [ serializers.DerivativesStatus.parse(*([symbol] + sub_data)) for sub_data in data ]
+
+    def get_liquidations(self,
+                         *,
+                         sort: Optional[Sort] = None,
+                         start: Optional[str] = None,
+                         end: Optional[str] = None,
+                         limit: Optional[int] = None) -> List[Liquidation]:
+        params = { "sort": sort, "start": start, "end": end, "limit": limit }
+        data = self._get("liquidations/hist", params=params)
+        return [ serializers.Liquidation.parse(*sub_data[0]) for sub_data in data ]
+
+    def get_seed_candles(self,
+                         symbol: str,
+                         tf: str = "1m",
+                         *,
+                         sort: Optional[Sort] = None,
+                         start: Optional[str] = None,
+                         end: Optional[str] = None,
+                         limit: Optional[int] = None) -> List[Candle]:
+        params = {"sort": sort, "start": start, "end": end, "limit": limit}
+        data = self._get(f"candles/trade:{tf}:{symbol}/hist", params=params)
+        return [ serializers.Candle.parse(*sub_data) for sub_data in data ]
+
+    def get_leaderboards_hist(self,
+                              resource: str,
+                              *,
+                              sort: Optional[Sort] = None,
+                              start: Optional[str] = None,
+                              end: Optional[str] = None,
+                              limit: Optional[int] = None) -> List[Leaderboard]:
+        params = { "sort": sort, "start": start, "end": end, "limit": limit }
+        data = self._get(f"rankings/{resource}/hist", params=params)
+        return [ serializers.Leaderboard.parse(*sub_data) for sub_data in data ]
+
+    def get_leaderboards_last(self,
+                              resource: str,
+                              *,
+                              sort: Optional[Sort] = None,
+                              start: Optional[str] = None,
+                              end: Optional[str] = None,
+                              limit: Optional[int] = None) -> Leaderboard:
+        params = { "sort": sort, "start": start, "end": end, "limit": limit }
+        data = self._get(f"rankings/{resource}/last", params=params)
+        return serializers.Leaderboard.parse(*data)
+
+    def get_funding_stats(self,
+                          symbol: str,
+                          *,
+                          start: Optional[str] = None,
+                          end: Optional[str] = None,
+                          limit: Optional[int] = None) -> List[FundingStatistic]:
+        params = { "start": start, "end": end, "limit": limit }
+        data = self._get(f"funding/stats/{symbol}/hist", params=params)
+        return [ serializers.FundingStatistic.parse(*sub_data) for sub_data in data ]
+
+    def get_pulse_profile_details(self, nickname: str) -> PulseProfile:
+        return serializers.PulseProfile.parse(*self._get(f"pulse/profile/{nickname}"))
+
+    def get_pulse_message_history(self,
+                                  *,
+                                  end: Optional[str] = None,
+                                  limit: Optional[int] = None) -> List[PulseMessage]:
+        messages = []
+
+        for subdata in self._get("pulse/hist", params={ "end": end, "limit": limit }):
+            subdata[18] = subdata[18][0]
+            message = serializers.PulseMessage.parse(*subdata)
+            messages.append(message)
+
+        return messages
+
+    def get_trading_market_average_price(self,
+                                         symbol: str,
+                                         amount: Union[Decimal, float, str],
+                                         *,
+                                         price_limit: Optional[Union[Decimal, float, str]] = None
+                                        ) -> TradingMarketAveragePrice:
+        return serializers.TradingMarketAveragePrice.parse(*self._post("calc/trade/avg", body={
+            "symbol": symbol, "amount": amount, "price_limit": price_limit
+        }))
+
+    def get_funding_market_average_price(self,
+                                         symbol: str,
+                                         amount: Union[Decimal, float, str],
+                                         period: int,
+                                         *,
+                                         rate_limit: Optional[Union[Decimal, float, str]] = None
+                                        ) -> FundingMarketAveragePrice:
+        return serializers.FundingMarketAveragePrice.parse(*self._post("calc/trade/avg", body={
+            "symbol": symbol, "amount": amount, "period": period, "rate_limit": rate_limit
+        }))
+
+    def get_fx_rate(self, ccy1: str, ccy2: str) -> FxRate:
+        return serializers.FxRate.parse(*self._post("calc/fx", body={ "ccy1": ccy1, "ccy2": ccy2 }))
diff --git a/bfxapi/rest/enums.py b/bfxapi/rest/enums.py
new file mode 100644
index 00000000..17b37530
--- /dev/null
+++ b/bfxapi/rest/enums.py
@@ -0,0 +1,47 @@
+#pylint: disable-next=wildcard-import,unused-wildcard-import
+from ..enums import *
+
+class Config(str, Enum):
+    MAP_CURRENCY_SYM = "pub:map:currency:sym"
+    MAP_CURRENCY_LABEL = "pub:map:currency:label"
+    MAP_CURRENCY_UNIT = "pub:map:currency:unit"
+    MAP_CURRENCY_UNDL = "pub:map:currency:undl"
+    MAP_CURRENCY_POOL = "pub:map:currency:pool"
+    MAP_CURRENCY_EXPLORER = "pub:map:currency:explorer"
+    MAP_CURRENCY_TX_FEE = "pub:map:currency:tx:fee"
+    MAP_TX_METHOD = "pub:map:tx:method"
+
+    LIST_PAIR_EXCHANGE = "pub:list:pair:exchange"
+    LIST_PAIR_MARGIN = "pub:list:pair:margin"
+    LIST_PAIR_FUTURES = "pub:list:pair:futures"
+    LIST_PAIR_SECURITIES = "pub:list:pair:securities"
+    LIST_CURRENCY = "pub:list:currency"
+    LIST_COMPETITIONS = "pub:list:competitions"
+
+    INFO_PAIR = "pub:info:pair"
+    INFO_PAIR_FUTURES = "pub:info:pair:futures"
+    INFO_TX_STATUS = "pub:info:tx:status"
+
+    SPEC_MARGIN = "pub:spec:margin"
+    FEES = "pub:fees"
+
+class Precision(str, Enum):
+    P0 = "P0"
+    P1 = "P1"
+    P2 = "P2"
+    P3 = "P3"
+    P4 = "P4"
+
+class Sort(int, Enum):
+    ASCENDING = +1
+    DESCENDING = -1
+
+class MerchantSettingsKey(str, Enum):
+    PREFERRED_FIAT = "bfx_pay_preferred_fiat"
+    RECOMMEND_STORE = "bfx_pay_recommend_store"
+    NOTIFY_PAYMENT_COMPLETED = "bfx_pay_notify_payment_completed"
+    NOTIFY_PAYMENT_COMPLETED_EMAIL = "bfx_pay_notify_payment_completed_email"
+    NOTIFY_AUTOCONVERT_EXECUTED = "bfx_pay_notify_autoconvert_executed"
+    DUST_BALANCE_UI = "bfx_pay_dust_balance_ui"
+    MERCHANT_CUSTOMER_SUPPORT_URL = "bfx_pay_merchant_customer_support_url"
+    MERCHANT_UNDERPAID_THRESHOLD = "bfx_pay_merchant_underpaid_threshold"
diff --git a/bfxapi/rest/exceptions.py b/bfxapi/rest/exceptions.py
new file mode 100644
index 00000000..8ea47c3a
--- /dev/null
+++ b/bfxapi/rest/exceptions.py
@@ -0,0 +1,35 @@
+from .. exceptions import BfxBaseException
+
+__all__ = [
+    "BfxRestException",
+
+    "ResourceNotFound",
+    "RequestParametersError",
+    "ResourceNotFound",
+    "InvalidAuthenticationCredentials"
+]
+
+class BfxRestException(BfxBaseException):
+    """
+    Base class for all custom exceptions in bfxapi/rest/exceptions.py.
+    """
+
+class ResourceNotFound(BfxRestException):
+    """
+    This error indicates a failed HTTP request to a non-existent resource.
+    """
+
+class RequestParametersError(BfxRestException):
+    """
+    This error indicates that there are some invalid parameters sent along with an HTTP request.
+    """
+
+class InvalidAuthenticationCredentials(BfxRestException):
+    """
+    This error indicates that the user has provided incorrect credentials (API-KEY and API-SECRET) for authentication.
+    """
+
+class UnknownGenericError(BfxRestException):
+    """
+    This error indicates an undefined problem processing an HTTP request sent to the APIs.
+    """
diff --git a/bfxapi/rest/middleware/__init__.py b/bfxapi/rest/middleware/__init__.py
new file mode 100644
index 00000000..ae3488de
--- /dev/null
+++ b/bfxapi/rest/middleware/__init__.py
@@ -0,0 +1,1 @@
+from .middleware import Middleware
diff --git a/bfxapi/rest/middleware/middleware.py b/bfxapi/rest/middleware/middleware.py
new file mode 100644
index 00000000..4bfe8b08
--- /dev/null
+++ b/bfxapi/rest/middleware/middleware.py
@@ -0,0 +1,99 @@
+from typing import TYPE_CHECKING, Optional, Any
+
+from http import HTTPStatus
+
+import time, hmac, hashlib, json, requests
+
+from ..enums import Error
+from ..exceptions import ResourceNotFound, RequestParametersError, InvalidAuthenticationCredentials, UnknownGenericError
+from ...utils.json_encoder import JSONEncoder
+
+if TYPE_CHECKING:
+    from requests.sessions import _Params
+
+class Middleware:
+    TIMEOUT = 30
+
+    def __init__(self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None):
+        self.host, self.api_key, self.api_secret = host, api_key, api_secret
+
+    def __build_authentication_headers(self, endpoint: str, data: Optional[str] = None):
+        assert isinstance(self.api_key, str) and isinstance(self.api_secret, str), \
+            "API_KEY and API_SECRET must be both str to call __build_authentication_headers"
+
+        nonce = str(round(time.time() * 1_000_000))
+
+        if data is None:
+            path = f"/api/v2/{endpoint}{nonce}"
+        else: path = f"/api/v2/{endpoint}{nonce}{data}"
+
+        signature = hmac.new(
+            self.api_secret.encode("utf8"),
+            path.encode("utf8"),
+            hashlib.sha384
+        ).hexdigest()
+
+        return {
+            "bfx-nonce": nonce,
+            "bfx-signature": signature,
+            "bfx-apikey": self.api_key
+        }
+
+    def _get(self, endpoint: str, params: Optional["_Params"] = None) -> Any:
+        response = requests.get(
+            url=f"{self.host}/{endpoint}",
+            params=params,
+            timeout=Middleware.TIMEOUT
+        )
+
+        if response.status_code == HTTPStatus.NOT_FOUND:
+            raise ResourceNotFound(f"No resources found at endpoint <{endpoint}>.")
+
+        data = response.json()
+
+        if len(data) and data[0] == "error":
+            if data[1] == Error.ERR_PARAMS:
+                raise RequestParametersError("The request was rejected with the " \
+                    f"following parameter error: <{data[2]}>")
+
+            if data[1] is None or data[1] == Error.ERR_UNK or data[1] == Error.ERR_GENERIC:
+                raise UnknownGenericError("The server replied to the request with " \
+                    f"a generic error with message: <{data[2]}>.")
+
+        return data
+
+    def _post(self, endpoint: str, params: Optional["_Params"] = None,
+            body: Optional[Any] = None, _ignore_authentication_headers: bool = False) -> Any:
+        data = body and json.dumps(body, cls=JSONEncoder) or None
+
+        headers = { "Content-Type": "application/json" }
+
+        if self.api_key and self.api_secret and not _ignore_authentication_headers:
+            headers = { **headers, **self.__build_authentication_headers(endpoint, data) }
+
+        response = requests.post(
+            url=f"{self.host}/{endpoint}",
+            params=params,
+            data=data,
+            headers=headers,
+            timeout=Middleware.TIMEOUT
+        )
+
+        if response.status_code == HTTPStatus.NOT_FOUND:
+            raise ResourceNotFound(f"No resources found at endpoint <{endpoint}>.")
+
+        data = response.json()
+
+        if isinstance(data, list) and len(data) and data[0] == "error":
+            if data[1] == Error.ERR_PARAMS:
+                raise RequestParametersError("The request was rejected with the " \
+                    f"following parameter error: <{data[2]}>")
+
+            if data[1] == Error.ERR_AUTH_FAIL:
+                raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.")
+
+            if data[1] is None or data[1] == Error.ERR_UNK or data[1] == Error.ERR_GENERIC:
+                raise UnknownGenericError("The server replied to the request with " \
+                    f"a generic error with message: <{data[2]}>.")
+
+        return data
diff --git a/bfxapi/rest/serializers.py b/bfxapi/rest/serializers.py
new file mode 100644
index 00000000..7cb8b717
--- /dev/null
+++ b/bfxapi/rest/serializers.py
@@ -0,0 +1,963 @@
+#pylint: disable=duplicate-code
+
+from . import types
+
+from .. labeler import generate_labeler_serializer, generate_recursive_serializer
+
+#pylint: disable-next=unused-import
+from .. notification import _Notification
+
+__serializers__ = [
+    "PlatformStatus", "TradingPairTicker", "FundingCurrencyTicker",
+    "TickersHistory", "TradingPairTrade", "FundingCurrencyTrade",
+    "TradingPairBook", "FundingCurrencyBook", "TradingPairRawBook",
+    "FundingCurrencyRawBook", "Statistic", "Candle",
+    "DerivativesStatus", "Liquidation", "Leaderboard",
+    "FundingStatistic", "PulseProfile", "PulseMessage",
+    "TradingMarketAveragePrice", "FundingMarketAveragePrice", "FxRate",
+
+    "UserInfo", "LoginHistory", "BalanceAvailable",
+    "Order", "Position", "Trade",
+    "FundingTrade", "OrderTrade", "Ledger",
+    "FundingOffer", "FundingCredit", "FundingLoan",
+    "FundingAutoRenew", "FundingInfo", "Wallet",
+    "Transfer", "Withdrawal", "DepositAddress",
+    "LightningNetworkInvoice", "Movement", "SymbolMarginInfo",
+    "BaseMarginInfo", "PositionClaim", "PositionIncreaseInfo",
+    "PositionIncrease", "PositionHistory", "PositionSnapshot",
+    "PositionAudit", "DerivativePositionCollateral", "DerivativePositionCollateralLimits",
+]
+
+#region Serializers definition for Rest Public Endpoints
+
+PlatformStatus = generate_labeler_serializer(
+    name="PlatformStatus",
+    klass=types.PlatformStatus,
+    labels=[
+        "status"
+    ]
+)
+
+TradingPairTicker = generate_labeler_serializer(
+    name="TradingPairTicker",
+    klass=types.TradingPairTicker,
+    labels=[
+        "symbol",
+        "bid",
+        "bid_size",
+        "ask",
+        "ask_size",
+        "daily_change",
+        "daily_change_relative",
+        "last_price",
+        "volume",
+        "high",
+        "low"
+    ]
+)
+
+FundingCurrencyTicker = generate_labeler_serializer(
+    name="FundingCurrencyTicker",
+    klass=types.FundingCurrencyTicker,
+    labels=[
+        "symbol",
+        "frr",
+        "bid",
+        "bid_period",
+        "bid_size",
+        "ask",
+        "ask_period",
+        "ask_size",
+        "daily_change",
+        "daily_change_relative",
+        "last_price",
+        "volume",
+        "high",
+        "low",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "frr_amount_available"
+    ]
+)
+
+TickersHistory = generate_labeler_serializer(
+    name="TickersHistory",
+    klass=types.TickersHistory,
+    labels=[
+        "symbol",
+        "bid",
+        "_PLACEHOLDER",
+        "ask",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "mts"
+    ]
+)
+
+TradingPairTrade = generate_labeler_serializer(
+    name="TradingPairTrade",
+    klass=types.TradingPairTrade,
+    labels=[
+        "id",
+        "mts",
+        "amount",
+        "price"
+    ]
+)
+
+FundingCurrencyTrade = generate_labeler_serializer(
+    name="FundingCurrencyTrade",
+    klass=types.FundingCurrencyTrade,
+    labels=[
+        "id",
+        "mts",
+        "amount",
+        "rate",
+        "period"
+    ]
+)
+
+TradingPairBook = generate_labeler_serializer(
+    name="TradingPairBook",
+    klass=types.TradingPairBook,
+    labels=[
+        "price",
+        "count",
+        "amount"
+    ]
+)
+
+FundingCurrencyBook = generate_labeler_serializer(
+    name="FundingCurrencyBook",
+    klass=types.FundingCurrencyBook,
+    labels=[
+        "rate",
+        "period",
+        "count",
+        "amount"
+    ]
+)
+
+TradingPairRawBook = generate_labeler_serializer(
+    name="TradingPairRawBook",
+    klass=types.TradingPairRawBook,
+    labels=[
+        "order_id",
+        "price",
+        "amount"
+    ]
+)
+
+FundingCurrencyRawBook = generate_labeler_serializer(
+    name="FundingCurrencyRawBook",
+    klass=types.FundingCurrencyRawBook,
+    labels=[
+        "offer_id",
+        "period",
+        "rate",
+        "amount"
+    ]
+)
+
+Statistic = generate_labeler_serializer(
+    name="Statistic",
+    klass=types.Statistic,
+    labels=[
+        "mts",
+        "value"
+    ]
+)
+
+Candle = generate_labeler_serializer(
+    name="Candle",
+    klass=types.Candle,
+    labels=[
+        "mts",
+        "open",
+        "close",
+        "high",
+        "low",
+        "volume"
+    ]
+)
+
+DerivativesStatus = generate_labeler_serializer(
+    name="DerivativesStatus",
+    klass=types.DerivativesStatus,
+    labels=[
+        "key",
+        "mts",
+        "_PLACEHOLDER", 
+        "deriv_price",
+        "spot_price",
+        "_PLACEHOLDER",
+        "insurance_fund_balance",
+        "_PLACEHOLDER",
+        "next_funding_evt_mts",
+        "next_funding_accrued",
+        "next_funding_step",
+        "_PLACEHOLDER",
+        "current_funding",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "mark_price",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "open_interest",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "clamp_min",
+        "clamp_max"
+    ]
+)
+
+Liquidation = generate_labeler_serializer(
+    name="Liquidation",
+    klass=types.Liquidation,
+    labels=[
+        "_PLACEHOLDER",
+        "pos_id",
+        "mts",
+        "_PLACEHOLDER",
+        "symbol",
+        "amount",
+        "base_price",
+        "_PLACEHOLDER",
+        "is_match",
+        "is_market_sold",
+        "_PLACEHOLDER",
+        "price_acquired"
+    ]
+)
+
+Leaderboard = generate_labeler_serializer(
+    name="Leaderboard",
+    klass=types.Leaderboard,
+    labels=[
+        "mts",
+        "_PLACEHOLDER",
+        "username",
+        "ranking",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "value",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "twitter_handle"
+    ]
+)
+
+FundingStatistic = generate_labeler_serializer(
+    name="FundingStatistic",
+    klass=types.FundingStatistic,
+    labels=[
+        "mts",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "frr",
+        "avg_period",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "funding_amount",
+        "funding_amount_used",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "funding_below_threshold"
+    ]
+)
+
+PulseProfile = generate_labeler_serializer(
+    name="PulseProfile",
+    klass=types.PulseProfile,
+    labels=[
+        "puid",
+        "mts",
+        "_PLACEHOLDER",
+        "nickname",
+        "_PLACEHOLDER",
+        "picture",
+        "text",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "twitter_handle",
+        "_PLACEHOLDER",
+        "followers",
+        "following",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "tipping_status"
+    ]
+)
+
+PulseMessage = generate_recursive_serializer(
+    name="PulseMessage",
+    klass=types.PulseMessage,
+    serializers={ "profile": PulseProfile },
+    labels=[
+        "pid",
+        "mts",
+        "_PLACEHOLDER",
+        "puid",
+        "_PLACEHOLDER",
+        "title",
+        "content",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "is_pin",
+        "is_public",
+        "comments_disabled",
+        "tags", 
+        "attachments",
+        "meta",
+        "likes",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "profile",
+        "comments",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER"
+    ]
+)
+
+TradingMarketAveragePrice = generate_labeler_serializer(
+    name="TradingMarketAveragePrice",
+    klass=types.TradingMarketAveragePrice,
+    labels=[
+        "price_avg",
+        "amount"
+    ]
+)
+
+FundingMarketAveragePrice = generate_labeler_serializer(
+    name="FundingMarketAveragePrice",
+    klass=types.FundingMarketAveragePrice,
+    labels=[
+        "rate_avg",
+        "amount"
+    ]
+)
+
+FxRate = generate_labeler_serializer(
+    name="FxRate",
+    klass=types.FxRate,
+    labels=[
+        "current_rate"
+    ]
+)
+
+#endregion
+
+#region Serializers definition for Rest Authenticated Endpoints
+
+UserInfo = generate_labeler_serializer(
+    name="UserInfo",
+    klass=types.UserInfo,
+    labels=[
+        "id",
+        "email",
+        "username",
+        "mts_account_create",
+        "verified",
+        "verification_level",
+        "_PLACEHOLDER",
+        "timezone",
+        "locale",
+        "company",
+        "email_verified",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "mts_master_account_create",
+        "group_id",
+        "master_account_id",
+        "inherit_master_account_verification",
+        "is_group_master",
+        "group_withdraw_enabled",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "ppt_enabled",
+        "merchant_enabled",
+        "competition_enabled",
+        "two_factors_authentication_modes",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "is_securities_master",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "securities_enabled",
+        "allow_disable_ctxswitch",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "time_last_login",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "ctxtswitch_disabled",
+        "_PLACEHOLDER",
+        "comp_countries",
+        "compl_countries_resid",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "is_merchant_enterprise"
+    ]
+)
+
+LoginHistory = generate_labeler_serializer(
+    name="LoginHistory",
+    klass=types.LoginHistory,
+    labels=[
+        "id",
+        "_PLACEHOLDER",
+        "time",
+        "_PLACEHOLDER",
+        "ip",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "extra_info"
+    ]
+)
+
+BalanceAvailable = generate_labeler_serializer(
+    name="BalanceAvailable",
+    klass=types.BalanceAvailable,
+    labels=[
+        "amount"
+    ]
+)
+
+Order = generate_labeler_serializer(
+    name="Order",
+    klass=types.Order,
+    labels=[
+        "id",
+        "gid",
+        "cid",
+        "symbol",
+        "mts_create", 
+        "mts_update", 
+        "amount", 
+        "amount_orig", 
+        "order_type",
+        "type_prev",
+        "mts_tif",
+        "_PLACEHOLDER",
+        "flags",
+        "order_status",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "price",
+        "price_avg",
+        "price_trailing",
+        "price_aux_limit",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "notify",
+        "hidden", 
+        "placed_id",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "routing",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "meta"
+    ]
+)
+
+Position = generate_labeler_serializer(
+    name="Position",
+    klass=types.Position,
+    labels=[
+        "symbol", 
+        "status", 
+        "amount", 
+        "base_price", 
+        "margin_funding", 
+        "margin_funding_type",
+        "pl",
+        "pl_perc",
+        "price_liq",
+        "leverage",
+        "_PLACEHOLDER",
+        "position_id",
+        "mts_create",
+        "mts_update",
+        "_PLACEHOLDER",
+        "type",
+        "_PLACEHOLDER",
+        "collateral",
+        "collateral_min",
+        "meta"
+    ]
+)
+
+Trade = generate_labeler_serializer(
+    name="Trade",
+    klass=types.Trade,
+    labels=[
+        "id", 
+        "symbol", 
+        "mts_create", 
+        "order_id", 
+        "exec_amount", 
+        "exec_price", 
+        "order_type", 
+        "order_price", 
+        "maker", 
+        "fee", 
+        "fee_currency",
+        "cid"
+    ]
+)
+
+FundingTrade = generate_labeler_serializer(
+    name="FundingTrade",
+    klass=types.FundingTrade,
+    labels=[
+        "id",
+        "currency",
+        "mts_create",
+        "offer_id",
+        "amount",
+        "rate",
+        "period"
+    ]
+)
+
+OrderTrade = generate_labeler_serializer(
+    name="OrderTrade",
+    klass=types.OrderTrade,
+    labels=[
+        "id",
+        "symbol",
+        "mts_create",
+        "order_id",
+        "exec_amount",
+        "exec_price",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "maker",
+        "fee",
+        "fee_currency",
+        "cid"
+    ]
+)
+
+Ledger = generate_labeler_serializer(
+    name="Ledger",
+    klass=types.Ledger,
+    labels=[
+        "id",
+        "currency",
+        "_PLACEHOLDER",
+        "mts",
+        "_PLACEHOLDER",
+        "amount",
+        "balance",
+        "_PLACEHOLDER",
+        "description"
+    ]
+)
+
+FundingOffer = generate_labeler_serializer(
+    name="FundingOffer",
+    klass=types.FundingOffer,
+    labels=[
+        "id",
+        "symbol",
+        "mts_create",
+        "mts_update",
+        "amount",
+        "amount_orig",
+        "offer_type",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "flags",
+        "offer_status",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "rate",
+        "period",
+        "notify",
+        "hidden",
+        "_PLACEHOLDER",
+        "renew",
+        "_PLACEHOLDER"
+    ]
+)
+
+FundingCredit = generate_labeler_serializer(
+    name="FundingCredit",
+    klass=types.FundingCredit,
+    labels=[
+        "id",
+        "symbol",
+        "side",
+        "mts_create",
+        "mts_update",
+        "amount",
+        "flags",
+        "status",
+        "rate_type",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "rate",
+        "period",
+        "mts_opening",
+        "mts_last_payout",
+        "notify",
+        "hidden",
+        "_PLACEHOLDER",
+        "renew",
+        "_PLACEHOLDER",
+        "no_close",
+        "position_pair"
+    ]
+)
+
+FundingLoan = generate_labeler_serializer(
+    name="FundingLoan",
+    klass=types.FundingLoan,
+    labels=[
+        "id",
+        "symbol",
+        "side",
+        "mts_create",
+        "mts_update",
+        "amount",
+        "flags",
+        "status",
+        "rate_type",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "rate",
+        "period",
+        "mts_opening",
+        "mts_last_payout",
+        "notify",
+        "hidden",
+        "_PLACEHOLDER",
+        "renew",
+        "_PLACEHOLDER",
+        "no_close"
+    ]
+)
+
+FundingAutoRenew = generate_labeler_serializer(
+    name="FundingAutoRenew",
+    klass=types.FundingAutoRenew,
+    labels=[
+        "currency",
+        "period",
+        "rate",
+        "threshold"
+    ]
+)
+
+FundingInfo = generate_labeler_serializer(
+    name="FundingInfo",
+    klass=types.FundingInfo,
+    labels=[
+        "yield_loan",
+        "yield_lend",
+        "duration_loan",
+        "duration_lend"
+    ]
+)
+
+Wallet = generate_labeler_serializer(
+    name="Wallet",
+    klass=types.Wallet,
+    labels=[
+        "wallet_type", 
+        "currency", 
+        "balance", 
+        "unsettled_interest",
+        "available_balance",
+        "last_change",
+        "trade_details"
+    ]
+)
+
+Transfer = generate_labeler_serializer(
+    name="Transfer",
+    klass=types.Transfer,
+    labels=[
+        "mts",
+        "wallet_from",
+        "wallet_to",
+        "_PLACEHOLDER",
+        "currency",
+        "currency_to",
+        "_PLACEHOLDER",
+        "amount"
+    ]
+)
+
+Withdrawal = generate_labeler_serializer(
+    name="Withdrawal",
+    klass=types.Withdrawal,
+    labels=[
+        "withdrawal_id",
+        "_PLACEHOLDER",
+        "method",
+        "payment_id",
+        "wallet",
+        "amount",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "withdrawal_fee"
+    ]
+)
+
+DepositAddress = generate_labeler_serializer(
+    name="DepositAddress",
+    klass=types.DepositAddress,
+    labels=[
+        "_PLACEHOLDER",
+        "method",
+        "currency_code",
+        "_PLACEHOLDER",
+        "address",
+        "pool_address"
+    ]
+)
+
+LightningNetworkInvoice = generate_labeler_serializer(
+    name="LightningNetworkInvoice",
+    klass=types.LightningNetworkInvoice,
+    labels=[
+        "invoice_hash",
+        "invoice",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "amount"
+    ]
+)
+
+Movement = generate_labeler_serializer(
+    name="Movement",
+    klass=types.Movement,
+    labels=[
+        "id",
+        "currency",
+        "currency_name",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "mts_start",
+        "mts_update",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "status",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "amount",
+        "fees",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "destination_address",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "transaction_id",
+        "withdraw_transaction_note"
+    ]
+)
+
+SymbolMarginInfo = generate_labeler_serializer(
+    name="SymbolMarginInfo",
+    klass=types.SymbolMarginInfo,
+    labels=[
+        "_PLACEHOLDER",
+        "symbol",
+        "tradable_balance",
+        "gross_balance",
+        "buy",
+        "sell"
+    ],
+
+    flat=True
+)
+
+BaseMarginInfo = generate_labeler_serializer(
+    name="BaseMarginInfo",
+    klass=types.BaseMarginInfo,
+    labels=[
+        "user_pl",
+        "user_swaps",
+        "margin_balance",
+        "margin_net",
+        "margin_min"
+    ]
+)
+
+PositionClaim = generate_labeler_serializer(
+    name="PositionClaim",
+    klass=types.PositionClaim,
+    labels=[
+        "symbol",
+        "position_status",
+        "amount",
+        "base_price",
+        "margin_funding",
+        "margin_funding_type",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "position_id",
+        "mts_create",
+        "mts_update",
+        "_PLACEHOLDER",
+        "pos_type",
+        "_PLACEHOLDER",
+        "collateral",
+        "min_collateral",
+        "meta"
+    ]
+)
+
+PositionIncreaseInfo = generate_labeler_serializer(
+    name="PositionIncreaseInfo",
+    klass=types.PositionIncreaseInfo,
+    labels=[
+        "max_pos",
+        "current_pos",
+        "base_currency_balance",
+        "tradable_balance_quote_currency",
+        "tradable_balance_quote_total",
+        "tradable_balance_base_currency",
+        "tradable_balance_base_total",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "funding_avail",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "funding_value",
+        "funding_required",
+        "funding_value_currency",
+        "funding_required_currency"
+    ],
+
+    flat=True
+)
+
+PositionIncrease = generate_labeler_serializer(
+    name="PositionIncrease",
+    klass=types.PositionIncrease,
+    labels=[
+        "symbol",
+        "_PLACEHOLDER",
+        "amount",
+        "base_price"
+    ]
+)
+
+PositionHistory = generate_labeler_serializer(
+    name="PositionHistory",
+    klass=types.PositionHistory,
+    labels=[
+        "symbol",
+        "status",
+        "amount",
+        "base_price",
+        "funding",
+        "funding_type",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "position_id",
+        "mts_create",
+        "mts_update"
+    ]
+)
+
+PositionSnapshot = generate_labeler_serializer(
+    name="PositionSnapshot",
+    klass=types.PositionSnapshot,
+    labels=[
+        "symbol",
+        "status",
+        "amount",
+        "base_price",
+        "funding",
+        "funding_type",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "position_id",
+        "mts_create",
+        "mts_update"
+    ]
+)
+
+PositionAudit = generate_labeler_serializer(
+    name="PositionAudit",
+    klass=types.PositionAudit,
+    labels=[
+        "symbol",
+        "status",
+        "amount",
+        "base_price",
+        "funding",
+        "funding_type",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "position_id",
+        "mts_create",
+        "mts_update",
+        "_PLACEHOLDER",
+        "type",
+        "_PLACEHOLDER",
+        "collateral",
+        "collateral_min",
+        "meta"
+    ]
+)
+
+DerivativePositionCollateral = generate_labeler_serializer(
+    name="DerivativePositionCollateral",
+    klass=types.DerivativePositionCollateral,
+    labels=[
+        "status"
+    ]
+)
+
+DerivativePositionCollateralLimits = generate_labeler_serializer(
+    name="DerivativePositionCollateralLimits",
+    klass=types.DerivativePositionCollateralLimits,
+    labels=[
+        "min_collateral",
+        "max_collateral"
+    ]
+)
+
+#endregion
diff --git a/bfxapi/rest/types.py b/bfxapi/rest/types.py
new file mode 100644
index 00000000..bea85c32
--- /dev/null
+++ b/bfxapi/rest/types.py
@@ -0,0 +1,702 @@
+#pylint: disable=duplicate-code
+
+#pylint: disable-next=wildcard-import,unused-wildcard-import
+from typing import *
+
+from dataclasses import dataclass
+
+from .. labeler import _Type, partial, compose
+
+#pylint: disable-next=unused-import
+from .. notification import Notification
+
+from ..utils.json_encoder import JSON
+
+#region Type hinting for Rest Public Endpoints
+
+@dataclass
+class PlatformStatus(_Type):
+    status: int
+
+@dataclass
+class TradingPairTicker(_Type):
+    symbol: str
+    bid: float
+    bid_size: float
+    ask: float
+    ask_size: float
+    daily_change: float
+    daily_change_relative: float
+    last_price: float
+    volume: float
+    high: float
+    low: float
+
+@dataclass
+class FundingCurrencyTicker(_Type):
+    symbol: str
+    frr: float
+    bid: float
+    bid_period: int
+    bid_size: float
+    ask: float
+    ask_period: int
+    ask_size: float
+    daily_change: float
+    daily_change_relative: float
+    last_price: float
+    volume: float
+    high: float
+    low: float
+    frr_amount_available: float
+
+@dataclass
+class TickersHistory(_Type):
+    symbol: str
+    bid: float
+    ask: float
+    mts: int
+
+@dataclass
+class TradingPairTrade(_Type):
+    id: int
+    mts: int
+    amount: float
+    price: float
+
+@dataclass
+class FundingCurrencyTrade(_Type):
+    id: int
+    mts: int
+    amount: float
+    rate: float
+    period: int
+
+@dataclass
+class TradingPairBook(_Type):
+    price: float
+    count: int
+    amount: float
+
+@dataclass
+class FundingCurrencyBook(_Type):
+    rate: float
+    period: int
+    count: int
+    amount: float
+
+@dataclass
+class TradingPairRawBook(_Type):
+    order_id: int
+    price: float
+    amount: float
+
+@dataclass
+class FundingCurrencyRawBook(_Type):
+    offer_id: int
+    period: int
+    rate: float
+    amount: float
+
+@dataclass
+class Statistic(_Type):
+    mts: int
+    value: float
+
+@dataclass
+class Candle(_Type):
+    mts: int
+    open: int
+    close: int
+    high: int
+    low: int
+    volume: float
+
+@dataclass
+class DerivativesStatus(_Type):
+    key: str
+    mts: int
+    deriv_price: float
+    spot_price: float
+    insurance_fund_balance: float
+    next_funding_evt_mts: int
+    next_funding_accrued: float
+    next_funding_step: int
+    current_funding: float
+    mark_price: float
+    open_interest: float
+    clamp_min: float
+    clamp_max: float
+
+@dataclass
+class Liquidation(_Type):
+    pos_id: int
+    mts: int
+    symbol: str
+    amount: float
+    base_price: float
+    is_match: int
+    is_market_sold: int
+    price_acquired: float
+
+@dataclass
+class Leaderboard(_Type):
+    mts: int
+    username: str
+    ranking: int
+    value: float
+    twitter_handle: Optional[str]
+
+@dataclass
+class FundingStatistic(_Type):
+    mts: int
+    frr: float
+    avg_period: float
+    funding_amount: float
+    funding_amount_used: float
+    funding_below_threshold: float
+
+@dataclass
+class PulseProfile(_Type):
+    puid: str
+    mts: int
+    nickname: str
+    picture: str
+    text: str
+    twitter_handle: str
+    followers: int
+    following: int
+    tipping_status: int
+
+@dataclass
+class PulseMessage(_Type):
+    pid: str
+    mts: int
+    puid: str
+    title: str
+    content: str
+    is_pin: int
+    is_public: int
+    comments_disabled: int
+    tags: List[str]
+    attachments: List[str]
+    meta: List[JSON]
+    likes: int
+    profile: PulseProfile
+    comments: int
+
+@dataclass
+class TradingMarketAveragePrice(_Type):
+    price_avg: float
+    amount: float
+
+@dataclass
+class FundingMarketAveragePrice(_Type):
+    rate_avg: float
+    amount: float
+
+@dataclass
+class FxRate(_Type):
+    current_rate: float
+
+#endregion
+
+#region Type hinting for Rest Authenticated Endpoints
+
+@dataclass
+class UserInfo(_Type):
+    id: int
+    email: str
+    username: str
+    mts_account_create: int
+    verified: int
+    verification_level: int
+    timezone: str
+    locale: str
+    company: str
+    email_verified: int
+    mts_master_account_create: int
+    group_id: int
+    master_account_id: int
+    inherit_master_account_verification: int
+    is_group_master: int
+    group_withdraw_enabled: int
+    ppt_enabled: int
+    merchant_enabled: int
+    competition_enabled: int
+    two_factors_authentication_modes: List[str]
+    is_securities_master: int
+    securities_enabled: int
+    allow_disable_ctxswitch: int
+    time_last_login: int
+    ctxtswitch_disabled: int
+    comp_countries: List[str]
+    compl_countries_resid: List[str]
+    is_merchant_enterprise: int
+
+@dataclass
+class LoginHistory(_Type):
+    id: int
+    time: int
+    ip: str
+    extra_info: JSON
+
+@dataclass
+class BalanceAvailable(_Type):
+    amount: float
+
+@dataclass
+class Order(_Type):
+    id: int
+    gid: int
+    cid: int
+    symbol: str
+    mts_create: int
+    mts_update: int
+    amount: float
+    amount_orig: float
+    order_type: str
+    type_prev: str
+    mts_tif: int
+    flags: int
+    order_status: str
+    price: float
+    price_avg: float
+    price_trailing: float
+    price_aux_limit: float
+    notify: int
+    hidden: int
+    placed_id: int
+    routing: str
+    meta: JSON
+
+@dataclass
+class Position(_Type):
+    symbol: str
+    status: str
+    amount: float
+    base_price: float
+    margin_funding: float
+    margin_funding_type: int
+    pl: float
+    pl_perc: float
+    price_liq: float
+    leverage: float
+    position_id: int
+    mts_create: int
+    mts_update: int
+    type: int
+    collateral: float
+    collateral_min: float
+    meta: JSON
+
+@dataclass
+class Trade(_Type):
+    id: int
+    symbol: str
+    mts_create: int
+    order_id: int
+    exec_amount: float
+    exec_price: float
+    order_type: str
+    order_price: float
+    maker:int
+    fee: float
+    fee_currency: str
+    cid: int
+
+@dataclass()
+class FundingTrade(_Type):
+    id: int
+    currency: str
+    mts_create: int
+    offer_id: int
+    amount: float
+    rate: float
+    period: int
+
+@dataclass
+class OrderTrade(_Type):
+    id: int
+    symbol: str
+    mts_create: int
+    order_id: int
+    exec_amount: float
+    exec_price: float
+    maker:int
+    fee: float
+    fee_currency: str
+    cid: int
+
+@dataclass
+class Ledger(_Type):
+    id: int
+    currency: str
+    mts: int
+    amount: float
+    balance: float
+    description: str
+
+@dataclass
+class FundingOffer(_Type):
+    id: int
+    symbol: str
+    mts_create: int
+    mts_update: int
+    amount: float
+    amount_orig: float
+    offer_type: str
+    flags: int
+    offer_status: str
+    rate: float
+    period: int
+    notify: int
+    hidden: int
+    renew: int
+
+@dataclass
+class FundingCredit(_Type):
+    id: int
+    symbol: str
+    side: int
+    mts_create: int
+    mts_update: int
+    amount: float
+    flags: int
+    status: str
+    rate_type: str
+    rate: float
+    period: int
+    mts_opening: int
+    mts_last_payout: int
+    notify: int
+    hidden: int
+    renew: int
+    no_close: int
+    position_pair: str
+
+@dataclass
+class FundingLoan(_Type):
+    id: int
+    symbol: str
+    side: int
+    mts_create: int
+    mts_update: int
+    amount: float
+    flags: int
+    status: str
+    rate_type: str
+    rate: float
+    period: int
+    mts_opening: int
+    mts_last_payout: int
+    notify: int
+    hidden: int
+    renew: int
+    no_close: int
+
+@dataclass
+class FundingAutoRenew(_Type):
+    currency: str
+    period: int
+    rate: float
+    threshold: float
+
+@dataclass()
+class FundingInfo(_Type):
+    yield_loan: float
+    yield_lend: float
+    duration_loan: float
+    duration_lend: float
+
+@dataclass
+class Wallet(_Type):
+    wallet_type: str
+    currency: str
+    balance: float
+    unsettled_interest: float
+    available_balance: float
+    last_change: str
+    trade_details: JSON
+
+@dataclass
+class Transfer(_Type):
+    mts: int
+    wallet_from: str
+    wallet_to: str
+    currency: str
+    currency_to: str
+    amount: int
+
+@dataclass
+class Withdrawal(_Type):
+    withdrawal_id: int
+    method: str
+    payment_id: str
+    wallet: str
+    amount: float
+    withdrawal_fee: float
+
+@dataclass
+class DepositAddress(_Type):
+    method: str
+    currency_code: str
+    address: str
+    pool_address: str
+
+@dataclass
+class LightningNetworkInvoice(_Type):
+    invoice_hash: str
+    invoice: str
+    amount: str
+
+@dataclass
+class Movement(_Type):
+    id: str
+    currency: str
+    currency_name: str
+    mts_start: int
+    mts_update: int
+    status: str
+    amount: int
+    fees: int
+    destination_address: str
+    transaction_id: str
+    withdraw_transaction_note: str
+
+@dataclass
+class SymbolMarginInfo(_Type):
+    symbol: str
+    tradable_balance: float
+    gross_balance: float
+    buy: float
+    sell: float
+
+@dataclass
+class BaseMarginInfo(_Type):
+    user_pl: float
+    user_swaps: float
+    margin_balance: float
+    margin_net: float
+    margin_min: float
+
+@dataclass
+class PositionClaim(_Type):
+    symbol: str
+    position_status: str
+    amount: float
+    base_price: float
+    margin_funding: float
+    margin_funding_type: int
+    position_id: int
+    mts_create: int
+    mts_update: int
+    pos_type: int
+    collateral: str
+    min_collateral: str
+    meta: JSON
+
+@dataclass
+class PositionIncreaseInfo(_Type):
+    max_pos: int
+    current_pos: float
+    base_currency_balance: float
+    tradable_balance_quote_currency: float
+    tradable_balance_quote_total: float
+    tradable_balance_base_currency: float
+    tradable_balance_base_total: float
+    funding_avail: float
+    funding_value: float
+    funding_required: float
+    funding_value_currency: str
+    funding_required_currency: str
+
+@dataclass
+class PositionIncrease(_Type):
+    symbol: str
+    amount: float
+    base_price: float
+
+@dataclass
+class PositionHistory(_Type):
+    symbol: str
+    status: str
+    amount: float
+    base_price: float
+    funding: float
+    funding_type: int
+    position_id: int
+    mts_create: int
+    mts_update: int
+
+@dataclass
+class PositionSnapshot(_Type):
+    symbol: str
+    status: str
+    amount: float
+    base_price: float
+    funding: float
+    funding_type: int
+    position_id: int
+    mts_create: int
+    mts_update: int
+
+@dataclass
+class PositionAudit(_Type):
+    symbol: str
+    status: str
+    amount: float
+    base_price: float
+    funding: float
+    funding_type: int
+    position_id: int
+    mts_create: int
+    mts_update: int
+    type: int
+    collateral: float
+    collateral_min: float
+    meta: JSON
+
+@dataclass
+class DerivativePositionCollateral(_Type):
+    status: int
+
+@dataclass
+class DerivativePositionCollateralLimits(_Type):
+    min_collateral: float
+    max_collateral: float
+
+#endregion
+
+#region Type hinting for Rest Merchant Endpoints
+
+@compose(dataclass, partial)
+class InvoiceSubmission(_Type):
+    id: str
+    t: int
+    type: Literal["ECOMMERCE", "POS"]
+    duration: int
+    amount: float
+    currency: str
+    order_id: str
+    pay_currencies: List[str]
+    webhook: str
+    redirect_url: str
+    status: Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"]
+    customer_info: "CustomerInfo"
+    invoices: List["Invoice"]
+    payment: "Payment"
+    additional_payments: List["Payment"]
+    merchant_name: str
+
+    @classmethod
+    def parse(cls, data: Dict[str, Any]) -> "InvoiceSubmission":
+        if "customer_info" in data and data["customer_info"] is not None:
+            data["customer_info"] = InvoiceSubmission.CustomerInfo(**data["customer_info"])
+
+        for index, invoice in enumerate(data["invoices"]):
+            data["invoices"][index] = InvoiceSubmission.Invoice(**invoice)
+
+        if "payment" in data and data["payment"] is not None:
+            data["payment"] = InvoiceSubmission.Payment(**data["payment"])
+
+        if "additional_payments" in data and data["additional_payments"] is not None:
+            for index, additional_payment in enumerate(data["additional_payments"]):
+                data["additional_payments"][index] = InvoiceSubmission.Payment(**additional_payment)
+
+        return InvoiceSubmission(**data)
+
+    @compose(dataclass, partial)
+    class CustomerInfo:
+        nationality: str
+        resid_country: str
+        resid_state: str
+        resid_city: str
+        resid_zip_code: str
+        resid_street: str
+        resid_building_no: str
+        full_name: str
+        email: str
+        tos_accepted: bool
+
+    @compose(dataclass, partial)
+    class Invoice:
+        amount: float
+        currency: str
+        pay_currency: str
+        pool_currency: str
+        address: str
+        ext: JSON
+
+    @compose(dataclass, partial)
+    class Payment:
+        txid: str
+        amount: float
+        currency: str
+        method: str
+        status: Literal["CREATED", "COMPLETED", "PROCESSING"]
+        confirmations: int
+        created_at: str
+        updated_at: str
+        deposit_id: int
+        ledger_id: int
+        force_completed: bool
+        amount_diff: str
+
+@dataclass
+class InvoicePage(_Type):
+    page: int
+    page_size: int
+    sort: Literal["asc", "desc"]
+    sort_field: Literal["t", "amount", "status"]
+    total_pages: int
+    total_items: int
+    items: List[InvoiceSubmission]
+
+    @classmethod
+    def parse(cls, data: Dict[str, Any]) -> "InvoicePage":
+        for index, item in enumerate(data["items"]):
+            data["items"][index] = InvoiceSubmission.parse(item)
+
+        return InvoicePage(**data)
+
+@dataclass
+class InvoiceStats(_Type):
+    time: str
+    count: float
+
+@dataclass
+class CurrencyConversion(_Type):
+    base_currency: str
+    convert_currency: str
+    created: int
+
+@dataclass
+class MerchantDeposit(_Type):
+    id: int
+    invoice_id: Optional[str]
+    order_id: Optional[str]
+    type: Literal["ledger", "deposit"]
+    amount: float
+    t: int
+    txid: str
+    currency: str
+    method: str
+    pay_method: str
+
+@dataclass
+class MerchantUnlinkedDeposit(_Type):
+    id: int
+    method: str
+    currency: str
+    created_at: int
+    updated_at: int
+    amount: float
+    fee: float
+    txid: str
+    address: str
+    payment_id: Optional[int]
+    status: str
+    note: Optional[str]
+
+#endregion
diff --git a/bfxapi/urls.py b/bfxapi/urls.py
new file mode 100644
index 00000000..556e4d9a
--- /dev/null
+++ b/bfxapi/urls.py
@@ -0,0 +1,5 @@
+REST_HOST = "https://api.bitfinex.com/v2"
+PUB_REST_HOST = "https://api-pub.bitfinex.com/v2"
+
+WSS_HOST = "wss://api.bitfinex.com/ws/2"
+PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2"
diff --git a/bfxapi/utils/__init__.py b/bfxapi/utils/__init__.py
index 5b150bee..e69de29b 100644
--- a/bfxapi/utils/__init__.py
+++ b/bfxapi/utils/__init__.py
@@ -1,1 +0,0 @@
-NAME = 'utils'
diff --git a/bfxapi/utils/auth.py b/bfxapi/utils/auth.py
deleted file mode 100644
index a83a2f0c..00000000
--- a/bfxapi/utils/auth.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
-This module is used to house all of the functions which are used
-to handle the http authentication of the client
-"""
-
-import hashlib
-import hmac
-import time
-from ..models import Order
-
-def generate_auth_payload(API_KEY, API_SECRET):
-  """
-  Generate a signed payload
-
-  @return json Object headers
-  """
-  nonce = _gen_nonce()
-  authMsg, sig = _gen_signature(API_KEY, API_SECRET, nonce)
-
-  return {
-    'apiKey': API_KEY,
-    'authSig': sig,
-    'authNonce': nonce,
-    'authPayload': authMsg,
-    'event': 'auth'
-  }
-
-def generate_auth_headers(API_KEY, API_SECRET, path, body):
-  """
-  Generate headers for a signed payload
-  """
-  nonce = str(_gen_nonce())
-  signature = "/api/v2/{}{}{}".format(path, nonce, body)
-  h = hmac.new(API_SECRET.encode('utf8'), signature.encode('utf8'), hashlib.sha384)
-  signature = h.hexdigest()
-
-  return {
-    "bfx-nonce": nonce,
-    "bfx-apikey": API_KEY,
-    "bfx-signature": signature
-  }
-
-def _gen_signature(API_KEY, API_SECRET, nonce):
-  authMsg = 'AUTH{}'.format(nonce)
-  secret = API_SECRET.encode('utf8')
-  sig = hmac.new(secret, authMsg.encode('utf8'), hashlib.sha384).hexdigest()
-
-  return authMsg, sig
-
-def _gen_nonce():
-  return int(round(time.time() * 1000000))
-
-def gen_unique_cid():
-  return int(round(time.time() * 1000))
-
-def calculate_order_flags(hidden, close, reduce_only, post_only, oco):
-  flags = 0
-  flags = flags + Order.Flags.HIDDEN if hidden else flags
-  flags = flags + Order.Flags.CLOSE if close else flags
-  flags = flags + Order.Flags.REDUCE_ONLY if reduce_only else flags
-  flags = flags + Order.Flags.POST_ONLY if post_only else flags
-  flags = flags + Order.Flags.OCO if oco else flags
-  return flags
diff --git a/bfxapi/utils/camel_and_snake_case_helpers.py b/bfxapi/utils/camel_and_snake_case_helpers.py
new file mode 100644
index 00000000..38a7993f
--- /dev/null
+++ b/bfxapi/utils/camel_and_snake_case_helpers.py
@@ -0,0 +1,23 @@
+import re
+
+from typing import TypeVar, Callable, cast
+
+T = TypeVar("T")
+
+_to_snake_case: Callable[[str], str] = lambda string: re.sub(r"(?<!^)(?=[A-Z])", "_", string).lower()
+
+_to_camel_case: Callable[[str], str] = lambda string: \
+    (components := string.split("_"))[0] + str().join(c.title() for c in components[1:])
+
+def _scheme(data: T, adapter: Callable[[str], str]) -> T:
+    if isinstance(data, list):
+        return cast(T, [ _scheme(sub_data, adapter) for sub_data in data ])
+    if isinstance(data, dict):
+        return cast(T, { adapter(key): _scheme(value, adapter) for key, value in data.items() })
+    return data
+
+def to_snake_case_keys(dictionary: T) -> T:
+    return _scheme(dictionary, _to_snake_case)
+
+def to_camel_case_keys(dictionary: T) -> T:
+    return _scheme(dictionary, _to_camel_case)
diff --git a/bfxapi/utils/custom_logger.py b/bfxapi/utils/custom_logger.py
deleted file mode 100644
index 7411ccb0..00000000
--- a/bfxapi/utils/custom_logger.py
+++ /dev/null
@@ -1,100 +0,0 @@
-"""
-Module used to describe all of the different data types
-"""
-
-import logging
-
-RESET_SEQ = "\033[0m"
-COLOR_SEQ = "\033[1;%dm"
-BOLD_SEQ = "\033[1m"
-UNDERLINE_SEQ = "\033[04m"
-
-YELLOW = '\033[93m'
-WHITE = '\33[37m'
-BLUE = '\033[34m'
-LIGHT_BLUE = '\033[94m'
-RED = '\033[91m'
-GREY = '\33[90m'
-
-KEYWORD_COLORS = {
-    'WARNING': YELLOW,
-    'INFO': LIGHT_BLUE,
-    'DEBUG': WHITE,
-    'CRITICAL': YELLOW,
-    'ERROR': RED,
-    'TRADE': '\33[102m\33[30m'
-}
-
-def formatter_message(message, use_color = True):
-    """
-    Syntax highlight certain keywords
-    """
-    if use_color:
-        message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ)
-    else:
-        message = message.replace("$RESET", "").replace("$BOLD", "")
-    return message
-
-def format_word(message, word, color_seq, bold=False, underline=False):
-    """
-    Surround the given word with a sequence
-    """
-    replacer = color_seq + word + RESET_SEQ
-    if underline:
-        replacer = UNDERLINE_SEQ + replacer
-    if bold:
-        replacer = BOLD_SEQ + replacer
-    return message.replace(word, replacer)
-
-class Formatter(logging.Formatter):
-  """
-  This Formatted simply colors in the levelname i.e 'INFO', 'DEBUG'
-  """
-  def __init__(self, msg, use_color = True):
-    logging.Formatter.__init__(self, msg)
-    self.use_color = use_color
-
-  def format(self, record):
-    """
-    Format and highlight certain keywords
-    """
-    levelname = record.levelname
-    if self.use_color and levelname in KEYWORD_COLORS:
-        levelname_color = KEYWORD_COLORS[levelname] + levelname + RESET_SEQ
-        record.levelname = levelname_color
-    record.name = GREY + record.name + RESET_SEQ
-    return logging.Formatter.format(self, record)
-
-class CustomLogger(logging.Logger):
-    """
-    This adds extra logging functions such as logger.trade and also
-    sets the logger to use the custom formatter
-    """
-    FORMAT = "[$BOLD%(name)s$RESET] [%(levelname)s] %(message)s"
-    COLOR_FORMAT = formatter_message(FORMAT, True)
-    TRADE = 50
-
-    def __init__(self, name, logLevel='DEBUG'):
-        logging.Logger.__init__(self, name, logLevel)                
-        color_formatter = Formatter(self.COLOR_FORMAT)
-        console = logging.StreamHandler()
-        console.setFormatter(color_formatter)
-        self.addHandler(console)
-        logging.addLevelName(self.TRADE, "TRADE")
-        return
-
-    def set_level(self, level):
-        logging.Logger.setLevel(self, level)
-
-    
-    def trade(self, message, *args, **kws):
-        """
-        Print a syntax highlighted trade signal
-        """
-        if self.isEnabledFor(self.TRADE):
-            message = format_word(message, 'CLOSED ', YELLOW, bold=True)
-            message = format_word(message, 'OPENED ', LIGHT_BLUE, bold=True)
-            message = format_word(message, 'UPDATED ', BLUE, bold=True)
-            message = format_word(message, 'CLOSED_ALL ', RED, bold=True)
-            # Yes, logger takes its '*args' as 'args'.
-            self._log(self.TRADE, message, args, **kws) 
diff --git a/bfxapi/utils/decimal.py b/bfxapi/utils/decimal.py
deleted file mode 100644
index 679ae8b6..00000000
--- a/bfxapi/utils/decimal.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import decimal as dec
-
-class Decimal(dec.Decimal):
-
-    @classmethod
-    def from_float(cls, f):
-      return cls(str(f))
-
-    def __new__(cls, value=0, *args, **kwargs):
-      if isinstance(value, float):
-        value = Decimal.from_float(value)
-      return super(Decimal, cls).__new__(cls, value, *args, **kwargs)
-
-    def __mul__(self, rhs):
-      if isinstance(rhs, float):
-        rhs = Decimal.from_float(rhs)
-      return Decimal(super().__mul__(rhs))
-
-    def __rmul__(self, lhs):
-      return self.__mul__(lhs)
-
-    def __add__(self, rhs):
-      if isinstance(rhs, float):
-        rhs = Decimal.from_float(rhs)
-      return Decimal(super().__add__(rhs))
-
-    def __radd__(self, lhs):
-      return self.__add__(lhs)
-
-    def __sub__(self, rhs):
-      if isinstance(rhs, float):
-        rhs = Decimal.from_float(rhs)
-      return Decimal(super().__sub__(rhs))
-
-    def __rsub__(self, lhs):
-      return self.__sub__(lhs)
-
-    def __truediv__(self, rhs):
-      if isinstance(rhs, float):
-        rhs = Decimal.from_float(rhs)
-      return Decimal(super().__truediv__(rhs))
-
-    def __rtruediv__(self, rhs):
-      return self.__truediv__(rhs)
-
-    def __floordiv__(self, rhs):
-      if isinstance(rhs, float):
-        rhs = Decimal.from_float(rhs)
-      return Decimal(super().__floordiv__(rhs))
-
-    def __rfloordiv__ (self, rhs):
-      return self.__floordiv__(rhs)
diff --git a/bfxapi/utils/decorators.py b/bfxapi/utils/decorators.py
deleted file mode 100644
index 3a4fe9aa..00000000
--- a/bfxapi/utils/decorators.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from ..utils.custom_logger import CustomLogger
-
-
-def handle_failure(func):
-    async def inner_function(*args, **kwargs):
-        logger = CustomLogger('BfxWebsocket', logLevel="DEBUG")
-        try:
-            await func(*args, **kwargs)
-        except Exception as exception_message:
-            logger.error(exception_message)
-
-    return inner_function
diff --git a/bfxapi/utils/json_encoder.py b/bfxapi/utils/json_encoder.py
new file mode 100644
index 00000000..21f0b7e0
--- /dev/null
+++ b/bfxapi/utils/json_encoder.py
@@ -0,0 +1,31 @@
+import json
+from decimal import Decimal
+from datetime import datetime
+
+from typing import Type, List, Dict, Union, Any
+
+JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]]
+
+def _strip(dictionary: Dict) -> Dict:
+    return { key: value for key, value in dictionary.items() if value is not None }
+
+def _convert_float_to_str(data: JSON) -> JSON:
+    if isinstance(data, float):
+        return format(Decimal(repr(data)), "f")
+    if isinstance(data, list):
+        return [ _convert_float_to_str(sub_data) for sub_data in data ]
+    if isinstance(data, dict):
+        return _strip({ key: _convert_float_to_str(value) for key, value in data.items() })
+    return data
+
+class JSONEncoder(json.JSONEncoder):
+    def encode(self, o: JSON) -> str:
+        return json.JSONEncoder.encode(self, _convert_float_to_str(o))
+
+    def default(self, o: Any) -> Any:
+        if isinstance(o, Decimal):
+            return format(o, "f")
+        if isinstance(o, datetime):
+            return str(o)
+
+        return json.JSONEncoder.default(self, o)
diff --git a/bfxapi/utils/logger.py b/bfxapi/utils/logger.py
new file mode 100644
index 00000000..6ebac5a6
--- /dev/null
+++ b/bfxapi/utils/logger.py
@@ -0,0 +1,51 @@
+import logging, sys
+
+BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
+
+COLOR_SEQ, ITALIC_COLOR_SEQ = "\033[1;%dm", "\033[3;%dm"
+
+COLORS = {
+    "DEBUG": CYAN,
+    "INFO": BLUE,
+    "WARNING": YELLOW,
+    "ERROR": RED
+}
+
+RESET_SEQ = "\033[0m"
+
+class _ColorFormatter(logging.Formatter):
+    def __init__(self, msg, use_color = True):
+        logging.Formatter.__init__(self, msg, "%d-%m-%Y %H:%M:%S")
+
+        self.use_color = use_color
+
+    def format(self, record):
+        levelname = record.levelname
+        if self.use_color and levelname in COLORS:
+            record.name = ITALIC_COLOR_SEQ % (30 + BLACK) + record.name + RESET_SEQ
+            record.levelname = COLOR_SEQ % (30 + COLORS[levelname]) + levelname + RESET_SEQ
+        return logging.Formatter.format(self, record)
+
+class ColorLogger(logging.Logger):
+    FORMAT = "[%(name)s] [%(levelname)s] [%(asctime)s] %(message)s"
+
+    def __init__(self, name, level):
+        logging.Logger.__init__(self, name, level)
+
+        colored_formatter = _ColorFormatter(self.FORMAT, use_color=True)
+        handler = logging.StreamHandler(stream=sys.stderr)
+        handler.setFormatter(fmt=colored_formatter)
+
+        self.addHandler(hdlr=handler)
+
+class FileLogger(logging.Logger):
+    FORMAT = "[%(name)s] [%(levelname)s] [%(asctime)s] %(message)s"
+
+    def __init__(self, name, level, filename):
+        logging.Logger.__init__(self, name, level)
+
+        formatter = logging.Formatter(self.FORMAT)
+        handler = logging.FileHandler(filename=filename)
+        handler.setFormatter(fmt=formatter)
+
+        self.addHandler(hdlr=handler)
diff --git a/bfxapi/version.py b/bfxapi/version.py
index 5bb61bee..3996ce87 100644
--- a/bfxapi/version.py
+++ b/bfxapi/version.py
@@ -1,5 +1,1 @@
-"""
-This module contains the current version of the bfxapi lib
-"""
-
-__version__ = '2.0.6'
+__version__ = "3.0.0b1"
diff --git a/bfxapi/websocket/__init__.py b/bfxapi/websocket/__init__.py
new file mode 100644
index 00000000..1509ba74
--- /dev/null
+++ b/bfxapi/websocket/__init__.py
@@ -0,0 +1,1 @@
+from .client import BfxWebsocketClient, BfxWebsocketBucket, BfxWebsocketInputs
diff --git a/bfxapi/websocket/client/__init__.py b/bfxapi/websocket/client/__init__.py
new file mode 100644
index 00000000..e9acea9e
--- /dev/null
+++ b/bfxapi/websocket/client/__init__.py
@@ -0,0 +1,3 @@
+from .bfx_websocket_client import BfxWebsocketClient
+from .bfx_websocket_bucket import BfxWebsocketBucket
+from .bfx_websocket_inputs import BfxWebsocketInputs
diff --git a/bfxapi/websocket/client/bfx_websocket_bucket.py b/bfxapi/websocket/client/bfx_websocket_bucket.py
new file mode 100644
index 00000000..2d5e2484
--- /dev/null
+++ b/bfxapi/websocket/client/bfx_websocket_bucket.py
@@ -0,0 +1,113 @@
+from typing import Literal, TypeVar, Callable, cast
+
+import asyncio, json, uuid, websockets
+
+from ..handlers import PublicChannelsHandler
+
+from ..exceptions import ConnectionNotOpen, TooManySubscriptions
+
+_HEARTBEAT = "hb"
+
+F = TypeVar("F", bound=Callable[..., Literal[None]])
+
+def _require_websocket_connection(function: F) -> F:
+    async def wrapper(self, *args, **kwargs):
+        if self.websocket is None or not self.websocket.open:
+            raise ConnectionNotOpen("No open connection with the server.")
+
+        await function(self, *args, **kwargs)
+
+    return cast(F, wrapper)
+
+class BfxWebsocketBucket:
+    VERSION = 2
+
+    MAXIMUM_SUBSCRIPTIONS_AMOUNT = 25
+
+    def __init__(self, host, event_emitter):
+        self.host, self.event_emitter, self.on_open_event = host, event_emitter, asyncio.locks.Event()
+
+        self.websocket, self.subscriptions, self.pendings = None, {}, []
+
+        self.handler = PublicChannelsHandler(event_emitter=self.event_emitter)
+
+    async def connect(self):
+        reconnection = False
+
+        async for websocket in websockets.connect(self.host):
+            self.websocket = websocket
+
+            self.on_open_event.set()
+
+            if reconnection or (reconnection := False):
+                for pending in self.pendings:
+                    await self.websocket.send(json.dumps(pending))
+
+                for _, subscription in self.subscriptions.items():
+                    await self.subscribe(**subscription)
+
+                self.subscriptions.clear()
+
+            try:
+                async for message in websocket:
+                    message = json.loads(message)
+
+                    if isinstance(message, dict):
+                        if message["event"] == "subscribed" and (chan_id := message["chanId"]):
+                            self.pendings = \
+                                [ pending for pending in self.pendings if pending["subId"] != message["subId"] ]
+
+                            self.subscriptions[chan_id] = message
+
+                            self.event_emitter.emit("subscribed", message)
+                        elif message["event"] == "unsubscribed" and (chan_id := message["chanId"]):
+                            if message["status"] == "OK":
+                                del self.subscriptions[chan_id]
+                        elif message["event"] == "error":
+                            self.event_emitter.emit("wss-error", message["code"], message["msg"])
+
+                    if isinstance(message, list):
+                        if (chan_id := message[0]) and message[1] != _HEARTBEAT:
+                            self.handler.handle(self.subscriptions[chan_id], *message[1:])
+            except websockets.ConnectionClosedError as error:
+                if error.code == 1006:
+                    self.on_open_event.clear()
+                    reconnection = True
+                    continue
+
+                raise error
+
+            break
+
+    @_require_websocket_connection
+    async def subscribe(self, channel, sub_id=None, **kwargs):
+        if len(self.subscriptions) + len(self.pendings) == BfxWebsocketBucket.MAXIMUM_SUBSCRIPTIONS_AMOUNT:
+            raise TooManySubscriptions("The client has reached the maximum number of subscriptions.")
+
+        subscription = {
+            **kwargs,
+
+            "event": "subscribe",
+            "channel": channel,
+            "subId": sub_id or str(uuid.uuid4()),
+        }
+
+        self.pendings.append(subscription)
+
+        await self.websocket.send(json.dumps(subscription))
+
+    @_require_websocket_connection
+    async def unsubscribe(self, chan_id):
+        await self.websocket.send(json.dumps({
+            "event": "unsubscribe",
+            "chanId": chan_id
+        }))
+
+    @_require_websocket_connection
+    async def close(self, code=1000, reason=str()):
+        await self.websocket.close(code=code, reason=reason)
+
+    def get_chan_id(self, sub_id):
+        for subscription in self.subscriptions.values():
+            if subscription["subId"] == sub_id:
+                return subscription["chanId"]
diff --git a/bfxapi/websocket/client/bfx_websocket_client.py b/bfxapi/websocket/client/bfx_websocket_client.py
new file mode 100644
index 00000000..5071ab64
--- /dev/null
+++ b/bfxapi/websocket/client/bfx_websocket_client.py
@@ -0,0 +1,264 @@
+from typing import cast
+
+from collections import namedtuple
+
+from datetime import datetime
+
+import traceback, json, asyncio, hmac, hashlib, time, socket, random, websockets
+
+from pyee.asyncio import AsyncIOEventEmitter
+
+from .bfx_websocket_bucket import _HEARTBEAT, F, _require_websocket_connection, BfxWebsocketBucket
+
+from .bfx_websocket_inputs import BfxWebsocketInputs
+from ..handlers import PublicChannelsHandler, AuthenticatedChannelsHandler
+from ..exceptions import WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, \
+    ZeroConnectionsError, OutdatedClientVersion
+
+from ...utils.json_encoder import JSONEncoder
+
+from ...utils.logger import ColorLogger, FileLogger
+
+def _require_websocket_authentication(function: F) -> F:
+    async def wrapper(self, *args, **kwargs):
+        if hasattr(self, "authentication") and not self.authentication:
+            raise WebsocketAuthenticationRequired("To perform this action you need to " \
+                "authenticate using your API_KEY and API_SECRET.")
+
+        await _require_websocket_connection(function)(self, *args, **kwargs)
+
+    return cast(F, wrapper)
+
+class _Delay:
+    BACKOFF_MIN, BACKOFF_MAX = 1.92, 60.0
+
+    BACKOFF_INITIAL = 5.0
+
+    def __init__(self, backoff_factor):
+        self.__backoff_factor = backoff_factor
+        self.__backoff_delay = _Delay.BACKOFF_MIN
+        self.__initial_delay = random.random() * _Delay.BACKOFF_INITIAL
+
+    def next(self):
+        backoff_delay = self.peek()
+        __backoff_delay = self.__backoff_delay * self.__backoff_factor
+        self.__backoff_delay = min(__backoff_delay, _Delay.BACKOFF_MAX)
+
+        return backoff_delay
+
+    def peek(self):
+        return (self.__backoff_delay == _Delay.BACKOFF_MIN) \
+            and self.__initial_delay or self.__backoff_delay
+
+class BfxWebsocketClient:
+    VERSION = BfxWebsocketBucket.VERSION
+
+    MAXIMUM_CONNECTIONS_AMOUNT = 20
+
+    EVENTS = [
+        "open", "subscribed", "authenticated", "wss-error",
+        *PublicChannelsHandler.EVENTS,
+        *AuthenticatedChannelsHandler.EVENTS
+    ]
+
+    def __init__(self, host, credentials = None, log_filename = None, log_level = "INFO"):
+        self.websocket, self.buckets, self.authentication = None, [], False
+
+        self.host, self.credentials, self.event_emitter = host, credentials, AsyncIOEventEmitter()
+
+        self.inputs = BfxWebsocketInputs(handle_websocket_input=self.__handle_websocket_input)
+
+        self.handler = AuthenticatedChannelsHandler(event_emitter=self.event_emitter)
+
+        if log_filename is None:
+            self.logger = ColorLogger("BfxWebsocketClient", level=log_level)
+        else: self.logger = FileLogger("BfxWebsocketClient", level=log_level, filename=log_filename)
+
+        self.event_emitter.add_listener("error",
+            lambda exception: self.logger.error(f"{type(exception).__name__}: {str(exception)}" + "\n" +
+                str().join(traceback.format_exception(type(exception), exception, exception.__traceback__))[:-1])
+        )
+
+    def run(self, connections = 5):
+        return asyncio.run(self.start(connections))
+
+    async def start(self, connections = 5):
+        if connections == 0:
+            self.logger.info("With connections set to 0 it will not be possible to subscribe to any public channel. " \
+                    "Attempting a subscription will cause a ZeroConnectionsError to be thrown.")
+
+        if connections > BfxWebsocketClient.MAXIMUM_CONNECTIONS_AMOUNT:
+            self.logger.warning(f"It is not safe to use more than {BfxWebsocketClient.MAXIMUM_CONNECTIONS_AMOUNT} " \
+                    f"buckets from the same connection ({connections} in use), the server could momentarily " \
+                        "block the client with <429 Too Many Requests>.")
+
+        for _ in range(connections):
+            self.buckets += [BfxWebsocketBucket(self.host, self.event_emitter)]
+
+        tasks = [ bucket.connect() for bucket in self.buckets ] + [ self.__connect() ]
+
+        await asyncio.gather(*tasks)
+
+    #pylint: disable-next=too-many-statements
+    async def __connect(self):
+        Reconnection = namedtuple("Reconnection", ["status", "attempts", "timestamp"])
+
+        reconnection, delay = Reconnection(status=False, attempts=0, timestamp=None), None
+
+        async def _connection():
+            nonlocal reconnection
+
+            async with websockets.connect(self.host) as websocket:
+                if reconnection.status:
+                    self.logger.info(f"Reconnect attempt successful (attempt no.{reconnection.attempts}): The " \
+                        f"client has been offline for a total of {datetime.now() - reconnection.timestamp} " \
+                            f"(connection lost at: {reconnection.timestamp:%d-%m-%Y at %H:%M:%S}).")
+
+                    reconnection = Reconnection(status=False, attempts=0, timestamp=None)
+
+                self.websocket, self.authentication = websocket, False
+
+                if len(self.buckets) == 0 or \
+                        (await asyncio.gather(*[bucket.on_open_event.wait() for bucket in self.buckets])):
+                    self.event_emitter.emit("open")
+
+                if self.credentials:
+                    await self.__authenticate(**self.credentials)
+
+                async for message in websocket:
+                    message = json.loads(message)
+
+                    if isinstance(message, dict):
+                        if message["event"] == "info" and "version" in message:
+                            if BfxWebsocketClient.VERSION != message["version"]:
+                                raise OutdatedClientVersion("Mismatch between the client version and the server " \
+                                    "version. Update the library to the latest version to continue (client version: " \
+                                        f"{BfxWebsocketClient.VERSION}, server version: {message['version']}).")
+                        elif message["event"] == "info" and message["code"] == 20051:
+                            rcvd = websockets.frames.Close(code=1012,
+                                reason="Stop/Restart Websocket Server (please reconnect).")
+
+                            raise websockets.ConnectionClosedError(rcvd=rcvd, sent=None)
+                        elif message["event"] == "auth":
+                            if message["status"] != "OK":
+                                raise InvalidAuthenticationCredentials(
+                                    "Cannot authenticate with given API-KEY and API-SECRET.")
+
+                            self.event_emitter.emit("authenticated", message)
+
+                            self.authentication = True
+                        elif message["event"] == "error":
+                            self.event_emitter.emit("wss-error", message["code"], message["msg"])
+
+                    if isinstance(message, list):
+                        if message[0] == 0 and message[1] != _HEARTBEAT:
+                            self.handler.handle(message[1], message[2])
+
+        while True:
+            if reconnection.status:
+                await asyncio.sleep(delay.next())
+
+            try:
+                await _connection()
+            except (websockets.ConnectionClosedError, socket.gaierror) as error:
+                if isinstance(error, websockets.ConnectionClosedError) and error.code in (1006, 1012):
+                    if error.code == 1006:
+                        self.logger.error("Connection lost: no close frame received " \
+                            "or sent (1006). Attempting to reconnect...")
+
+                    if error.code == 1012:
+                        self.logger.info("WSS server is about to restart, reconnection " \
+                            "required (client received 20051). Attempt in progress...")
+
+                    reconnection = Reconnection(status=True, attempts=1, timestamp=datetime.now())
+
+                    delay = _Delay(backoff_factor=1.618)
+                elif isinstance(error, socket.gaierror) and reconnection.status:
+                    self.logger.warning(f"Reconnection attempt no.{reconnection.attempts} has failed. " \
+                        f"Next reconnection attempt in ~{round(delay.peek()):.1f} seconds. (at the moment " \
+                            f"the client has been offline for {datetime.now() - reconnection.timestamp})")
+
+                    reconnection = reconnection._replace(attempts=reconnection.attempts + 1)
+                else: raise error
+
+            if not reconnection.status:
+                break
+
+    async def __authenticate(self, api_key, api_secret, filters=None):
+        data = { "event": "auth", "filter": filters, "apiKey": api_key }
+
+        data["authNonce"] = str(round(time.time() * 1_000_000))
+
+        data["authPayload"] = "AUTH" + data["authNonce"]
+
+        data["authSig"] = hmac.new(
+            api_secret.encode("utf8"),
+            data["authPayload"].encode("utf8"),
+            hashlib.sha384
+        ).hexdigest()
+
+        await self.websocket.send(json.dumps(data))
+
+    async def subscribe(self, channel, **kwargs):
+        if len(self.buckets) == 0:
+            raise ZeroConnectionsError("Unable to subscribe: the number of connections must be greater than 0.")
+
+        counters = [ len(bucket.pendings) + len(bucket.subscriptions) for bucket in self.buckets ]
+
+        index = counters.index(min(counters))
+
+        await self.buckets[index].subscribe(channel, **kwargs)
+
+    async def unsubscribe(self, sub_id):
+        for bucket in self.buckets:
+            if (chan_id := bucket.get_chan_id(sub_id)):
+                await bucket.unsubscribe(chan_id=chan_id)
+
+    async def close(self, code=1000, reason=str()):
+        if self.websocket is not None and self.websocket.open:
+            await self.websocket.close(code=code, reason=reason)
+
+        for bucket in self.buckets:
+            await bucket.close(code=code, reason=reason)
+
+    @_require_websocket_authentication
+    async def notify(self, info, message_id=None, **kwargs):
+        await self.websocket.send(json.dumps([ 0, "n", message_id, { "type": "ucm-test", "info": info, **kwargs } ]))
+
+    @_require_websocket_authentication
+    async def __handle_websocket_input(self, event, data):
+        await self.websocket.send(json.dumps([ 0, event, None, data], cls=JSONEncoder))
+
+    def on(self, *events, callback = None):
+        for event in events:
+            if event not in BfxWebsocketClient.EVENTS:
+                raise EventNotSupported(f"Event <{event}> is not supported. To get a list " \
+                            "of available events print BfxWebsocketClient.EVENTS")
+
+        if callback is not None:
+            for event in events:
+                self.event_emitter.on(event, callback)
+
+        if callback is None:
+            def handler(function):
+                for event in events:
+                    self.event_emitter.on(event, function)
+
+            return handler
+
+    def once(self, *events, callback = None):
+        for event in events:
+            if event not in BfxWebsocketClient.EVENTS:
+                raise EventNotSupported(f"Event <{event}> is not supported. To get a list " \
+                            "of available events print BfxWebsocketClient.EVENTS")
+
+        if callback is not None:
+            for event in events:
+                self.event_emitter.once(event, callback)
+
+        if callback is None:
+            def handler(function):
+                for event in events:
+                    self.event_emitter.once(event, function)
+
+            return handler
diff --git a/bfxapi/websocket/client/bfx_websocket_inputs.py b/bfxapi/websocket/client/bfx_websocket_inputs.py
new file mode 100644
index 00000000..f306bcb8
--- /dev/null
+++ b/bfxapi/websocket/client/bfx_websocket_inputs.py
@@ -0,0 +1,94 @@
+from decimal import Decimal
+from datetime import datetime
+
+from typing import Union, Optional, List, Tuple
+from .. enums import OrderType, FundingOfferType
+from ...utils.json_encoder import JSON
+
+class BfxWebsocketInputs:
+    def __init__(self, handle_websocket_input):
+        self.__handle_websocket_input = handle_websocket_input
+
+    async def submit_order(self,
+                    type: OrderType,
+                    symbol: str,
+                    amount: Union[Decimal, float, str],
+                    *,
+                    price: Optional[Union[Decimal, float, str]] = None,
+                    lev: Optional[int] = None,
+                    price_trailing: Optional[Union[Decimal, float, str]] = None,
+                    price_aux_limit: Optional[Union[Decimal, float, str]] = None,
+                    price_oco_stop: Optional[Union[Decimal, float, str]] = None,
+                    gid: Optional[int] = None,
+                    cid: Optional[int] = None,
+                    flags: Optional[int] = 0,
+                    tif: Optional[Union[datetime, str]] = None,
+                    meta: Optional[JSON] = None):
+        await self.__handle_websocket_input("on", {
+            "type": type, "symbol": symbol, "amount": amount,
+            "price": price, "lev": lev, "price_trailing": price_trailing,
+            "price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop, "gid": gid,
+            "cid": cid, "flags": flags, "tif": tif,
+            "meta": meta
+        })
+
+    async def update_order(self,
+                    id: int,
+                    *,
+                    amount: Optional[Union[Decimal, float, str]] = None,
+                    price: Optional[Union[Decimal, float, str]] = None,
+                    cid: Optional[int] = None,
+                    cid_date: Optional[str] = None,
+                    gid: Optional[int] = None,
+                    flags: Optional[int] = 0,
+                    lev: Optional[int] = None,
+                    delta: Optional[Union[Decimal, float, str]] = None,
+                    price_aux_limit: Optional[Union[Decimal, float, str]] = None,
+                    price_trailing: Optional[Union[Decimal, float, str]] = None,
+                    tif: Optional[Union[datetime, str]] = None):
+        await self.__handle_websocket_input("ou", {
+            "id": id, "amount": amount, "price": price,
+            "cid": cid, "cid_date": cid_date, "gid": gid,
+            "flags": flags, "lev": lev, "delta": delta,
+            "price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif
+        })
+
+    async def cancel_order(self,
+                    *,
+                    id: Optional[int] = None,
+                    cid: Optional[int] = None,
+                    cid_date: Optional[str] = None):
+        await self.__handle_websocket_input("oc", {
+            "id": id, "cid": cid, "cid_date": cid_date 
+        })
+
+    async def cancel_order_multi(self,
+                    *,
+                    ids: Optional[List[int]] = None,
+                    cids: Optional[List[Tuple[int, str]]] = None,
+                    gids: Optional[List[int]] = None,
+                    all: bool = False):
+        await self.__handle_websocket_input("oc_multi", {
+            "ids": ids, "cids": cids, "gids": gids,
+            "all": int(all)
+        })
+
+    #pylint: disable-next=too-many-arguments
+    async def submit_funding_offer(self,
+                    type: FundingOfferType,
+                    symbol: str,
+                    amount: Union[Decimal, float, str],
+                    rate: Union[Decimal, float, str],
+                    period: int,
+                    *,
+                    flags: Optional[int] = 0):
+        await self.__handle_websocket_input("fon", {
+            "type": type, "symbol": symbol, "amount": amount,
+            "rate": rate, "period": period, "flags": flags
+        })
+
+    async def cancel_funding_offer(self, id: int):
+        await self.__handle_websocket_input("foc", { "id": id })
+
+    async def calc(self, *args: str):
+        await self.__handle_websocket_input("calc", list(map(lambda arg: [arg], args)))
diff --git a/bfxapi/websocket/enums.py b/bfxapi/websocket/enums.py
new file mode 100644
index 00000000..2ff57ca2
--- /dev/null
+++ b/bfxapi/websocket/enums.py
@@ -0,0 +1,9 @@
+#pylint: disable-next=wildcard-import,unused-wildcard-import
+from .. enums import *
+
+class Channel(str, Enum):
+    TICKER = "ticker"
+    TRADES = "trades"
+    BOOK = "book"
+    CANDLES = "candles"
+    STATUS = "status"
diff --git a/bfxapi/websocket/exceptions.py b/bfxapi/websocket/exceptions.py
new file mode 100644
index 00000000..e1ff53ea
--- /dev/null
+++ b/bfxapi/websocket/exceptions.py
@@ -0,0 +1,60 @@
+from .. exceptions import BfxBaseException
+
+__all__ = [
+    "BfxWebsocketException",
+
+    "ConnectionNotOpen",
+    "TooManySubscriptions",
+    "ZeroConnectionsError",
+    "WebsocketAuthenticationRequired",
+    "InvalidAuthenticationCredentials",
+    "EventNotSupported",
+    "HandlerNotFound",
+    "OutdatedClientVersion"
+]
+
+class BfxWebsocketException(BfxBaseException):
+    """
+    Base class for all custom exceptions in bfxapi/websocket/exceptions.py.
+    """
+
+class ConnectionNotOpen(BfxWebsocketException):
+    """
+    This error indicates an attempt to communicate via websocket before starting the connection with the servers.
+    """
+
+class TooManySubscriptions(BfxWebsocketException):
+    """
+    This error indicates a subscription attempt after reaching the limit of simultaneous connections.
+    """
+
+class ZeroConnectionsError(BfxWebsocketException):
+    """
+    This error indicates an attempt to subscribe to a public channel while the number of connections is 0.
+    """
+
+class WebsocketAuthenticationRequired(BfxWebsocketException):
+    """
+    This error indicates an attempt to access a protected resource without logging in first.
+    """
+
+class InvalidAuthenticationCredentials(BfxWebsocketException):
+    """
+    This error indicates that the user has provided incorrect credentials (API-KEY and API-SECRET) for authentication.
+    """
+
+class EventNotSupported(BfxWebsocketException):
+    """
+    This error indicates a failed attempt to subscribe to an event not supported by the BfxWebsocketClient.
+    """
+
+
+class HandlerNotFound(BfxWebsocketException):
+    """
+    This error indicates that a handler was not found for an incoming message.
+    """
+
+class OutdatedClientVersion(BfxWebsocketException):
+    """
+    This error indicates a mismatch between the client version and the server WSS version.
+    """
diff --git a/bfxapi/websocket/handlers/__init__.py b/bfxapi/websocket/handlers/__init__.py
new file mode 100644
index 00000000..d55ea1e5
--- /dev/null
+++ b/bfxapi/websocket/handlers/__init__.py
@@ -0,0 +1,2 @@
+from .public_channels_handler import PublicChannelsHandler
+from .authenticated_channels_handler import AuthenticatedChannelsHandler
diff --git a/bfxapi/websocket/handlers/authenticated_channels_handler.py b/bfxapi/websocket/handlers/authenticated_channels_handler.py
new file mode 100644
index 00000000..221217e9
--- /dev/null
+++ b/bfxapi/websocket/handlers/authenticated_channels_handler.py
@@ -0,0 +1,69 @@
+from .. import serializers
+
+from .. serializers import _Notification
+
+from .. exceptions import HandlerNotFound
+
+class AuthenticatedChannelsHandler:
+    __abbreviations = {
+        "os": "order_snapshot", "on": "order_new", "ou": "order_update", 
+        "oc": "order_cancel", "ps": "position_snapshot", "pn": "position_new", 
+        "pu": "position_update", "pc": "position_close", "te": "trade_executed", 
+        "tu": "trade_execution_update", "fos": "funding_offer_snapshot", "fon": "funding_offer_new", 
+        "fou": "funding_offer_update", "foc": "funding_offer_cancel", "fcs": "funding_credit_snapshot", 
+        "fcn": "funding_credit_new", "fcu": "funding_credit_update", "fcc": "funding_credit_close",
+        "fls": "funding_loan_snapshot", "fln": "funding_loan_new", "flu": "funding_loan_update", 
+        "flc": "funding_loan_close", "ws": "wallet_snapshot", "wu": "wallet_update",
+        "bu": "balance_update",
+    }
+
+    __serializers = {
+        ("os", "on", "ou", "oc",): serializers.Order,
+        ("ps", "pn", "pu", "pc",): serializers.Position,
+        ("te", "tu"): serializers.Trade,
+        ("fos", "fon", "fou", "foc",): serializers.FundingOffer,
+        ("fcs", "fcn", "fcu", "fcc",): serializers.FundingCredit,
+        ("fls", "fln", "flu", "flc",): serializers.FundingLoan,
+        ("ws", "wu",): serializers.Wallet,
+        ("bu",): serializers.Balance
+    }
+
+    EVENTS = [
+        "notification", 
+        "on-req-notification", "ou-req-notification", "oc-req-notification",
+        "oc_multi-notification",
+        "fon-req-notification", "foc-req-notification",
+        *list(__abbreviations.values())
+    ]
+
+    def __init__(self, event_emitter):
+        self.event_emitter = event_emitter
+
+    def handle(self, abbrevation, stream):
+        if abbrevation == "n":
+            return self.__notification(stream)
+
+        for abbrevations, serializer in AuthenticatedChannelsHandler.__serializers.items():
+            if abbrevation in abbrevations:
+                event = AuthenticatedChannelsHandler.__abbreviations[abbrevation]
+
+                if all(isinstance(substream, list) for substream in stream):
+                    return self.event_emitter.emit(event, [ serializer.parse(*substream) for substream in stream ])
+
+                return self.event_emitter.emit(event, serializer.parse(*stream))
+
+        raise HandlerNotFound(f"No handler found for event of type <{abbrevation}>.")
+
+    def __notification(self, stream):
+        event, serializer = "notification", _Notification(serializer=None)
+
+        if stream[1] == "on-req" or stream[1] == "ou-req" or stream[1] == "oc-req":
+            event, serializer = f"{stream[1]}-notification", _Notification(serializer=serializers.Order)
+
+        if stream[1] == "oc_multi-req":
+            event, serializer = f"{stream[1]}-notification", _Notification(serializer=serializers.Order, iterate=True)
+
+        if stream[1] == "fon-req" or stream[1] == "foc-req":
+            event, serializer = f"{stream[1]}-notification", _Notification(serializer=serializers.FundingOffer)
+
+        return self.event_emitter.emit(event, serializer.parse(*stream))
diff --git a/bfxapi/websocket/handlers/public_channels_handler.py b/bfxapi/websocket/handlers/public_channels_handler.py
new file mode 100644
index 00000000..8439d52f
--- /dev/null
+++ b/bfxapi/websocket/handlers/public_channels_handler.py
@@ -0,0 +1,122 @@
+from .. import serializers
+
+from .. exceptions import HandlerNotFound
+
+class PublicChannelsHandler:
+    EVENTS = [
+        "t_ticker_update", "f_ticker_update", "t_trade_executed", "t_trade_execution_update", "f_trade_executed", 
+        "f_trade_execution_update", "t_trades_snapshot", "f_trades_snapshot", "t_book_snapshot", "f_book_snapshot", 
+        "t_raw_book_snapshot", "f_raw_book_snapshot", "t_book_update", "f_book_update", "t_raw_book_update", 
+        "f_raw_book_update", "candles_snapshot", "candles_update", "derivatives_status_update",
+    ]
+
+    def __init__(self, event_emitter):
+        self.event_emitter = event_emitter
+
+        self.__handlers = {
+            "ticker": self.__ticker_channel_handler,
+            "trades": self.__trades_channel_handler,
+            "book": self.__book_channel_handler,
+            "candles": self.__candles_channel_handler,
+            "status": self.__status_channel_handler
+        }
+
+    def handle(self, subscription, *stream):
+        #pylint: disable-next=unnecessary-lambda-assignment
+        _clear = lambda dictionary, *args: { key: value for key, value in dictionary.items() if key not in args }
+
+        #pylint: disable-next=consider-iterating-dictionary
+        if (channel := subscription["channel"]) and channel in self.__handlers.keys():
+            return self.__handlers[channel](_clear(subscription, "event", "channel", "chanId"), *stream)
+
+        raise HandlerNotFound(f"No handler found for channel <{subscription['channel']}>.")
+
+    def __ticker_channel_handler(self, subscription, *stream):
+        if subscription["symbol"].startswith("t"):
+            return self.event_emitter.emit(
+                "t_ticker_update",
+                subscription,
+                serializers.TradingPairTicker.parse(*stream[0])
+            )
+
+        if subscription["symbol"].startswith("f"):
+            return self.event_emitter.emit(
+                "f_ticker_update",
+                subscription,
+                serializers.FundingCurrencyTicker.parse(*stream[0])
+            )
+
+    def __trades_channel_handler(self, subscription, *stream):
+        if (event := stream[0]) and event in [ "te", "tu", "fte", "ftu" ]:
+            if subscription["symbol"].startswith("t"):
+                return self.event_emitter.emit(
+                    { "te": "t_trade_executed", "tu": "t_trade_execution_update" }[event],
+                    subscription,
+                    serializers.TradingPairTrade.parse(*stream[1])
+                )
+
+            if subscription["symbol"].startswith("f"):
+                return self.event_emitter.emit(
+                    { "fte": "f_trade_executed", "ftu": "f_trade_execution_update" }[event],
+                    subscription,
+                    serializers.FundingCurrencyTrade.parse(*stream[1])
+                )
+
+        if subscription["symbol"].startswith("t"):
+            return self.event_emitter.emit(
+                "t_trades_snapshot",
+                subscription,
+                [ serializers.TradingPairTrade.parse(*substream) for substream in stream[0] ]
+            )
+
+        if subscription["symbol"].startswith("f"):
+            return self.event_emitter.emit(
+                "f_trades_snapshot",
+                subscription,
+                [ serializers.FundingCurrencyTrade.parse(*substream)  for substream in stream[0] ]
+            )
+
+    def __book_channel_handler(self, subscription, *stream):
+        event = subscription["symbol"][0]
+
+        if subscription["prec"] == "R0":
+            _trading_pair_serializer, _funding_currency_serializer, is_raw_book = \
+                serializers.TradingPairRawBook, serializers.FundingCurrencyRawBook, True
+        else: _trading_pair_serializer, _funding_currency_serializer, is_raw_book = \
+                serializers.TradingPairBook, serializers.FundingCurrencyBook, False
+
+        if all(isinstance(substream, list) for substream in stream[0]):
+            return self.event_emitter.emit(
+                event + "_" + (is_raw_book and "raw_book" or "book") + "_snapshot",
+                subscription,
+                [ { "t": _trading_pair_serializer, "f": _funding_currency_serializer }[event] \
+                    .parse(*substream) for substream in stream[0] ]
+            )
+
+        return self.event_emitter.emit(
+            event + "_" + (is_raw_book and "raw_book" or "book") + "_update",
+            subscription,
+            { "t": _trading_pair_serializer, "f": _funding_currency_serializer }[event].parse(*stream[0])
+        )
+
+    def __candles_channel_handler(self, subscription, *stream):
+        if all(isinstance(substream, list) for substream in stream[0]):
+            return self.event_emitter.emit(
+                "candles_snapshot", 
+                subscription,
+                [ serializers.Candle.parse(*substream) for substream in stream[0] ]
+            )
+
+        return self.event_emitter.emit(
+            "candles_update",
+            subscription,
+            serializers.Candle.parse(*stream[0])
+        )
+
+    def __status_channel_handler(self, subscription, *stream):
+        if subscription["key"].startswith("deriv:"):
+            return self.event_emitter.emit(
+                "derivatives_status_update",
+                subscription,
+                serializers.DerivativesStatus.parse(*stream[0])
+            )
diff --git a/bfxapi/websocket/serializers.py b/bfxapi/websocket/serializers.py
new file mode 100644
index 00000000..464fdd92
--- /dev/null
+++ b/bfxapi/websocket/serializers.py
@@ -0,0 +1,368 @@
+#pylint: disable=duplicate-code
+
+from . import types
+
+from .. labeler import generate_labeler_serializer
+
+#pylint: disable-next=unused-import
+from .. notification import _Notification
+
+__serializers__ = [
+    "TradingPairTicker", "FundingCurrencyTicker", "TradingPairTrade",
+    "FundingCurrencyTrade", "TradingPairBook", "FundingCurrencyBook",
+    "TradingPairRawBook", "FundingCurrencyRawBook", "Candle",
+    "DerivativesStatus",
+
+    "Order", "Position", "Trade",
+    "FundingOffer", "FundingCredit", "FundingLoan",
+    "Wallet", "Balance",
+]
+
+#region Serializers definition for Websocket Public Channels
+
+TradingPairTicker = generate_labeler_serializer(
+    name="TradingPairTicker",
+    klass=types.TradingPairTicker,
+    labels=[
+        "bid",
+        "bid_size",
+        "ask",
+        "ask_size",
+        "daily_change",
+        "daily_change_relative",
+        "last_price",
+        "volume",
+        "high",
+        "low"
+    ]
+)
+
+FundingCurrencyTicker = generate_labeler_serializer(
+    name="FundingCurrencyTicker",
+    klass=types.FundingCurrencyTicker,
+    labels=[
+        "frr",
+        "bid",
+        "bid_period",
+        "bid_size",
+        "ask",
+        "ask_period",
+        "ask_size",
+        "daily_change",
+        "daily_change_relative",
+        "last_price",
+        "volume",
+        "high",
+        "low",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "frr_amount_available"
+    ]
+)
+
+TradingPairTrade = generate_labeler_serializer(
+    name="TradingPairTrade",
+    klass=types.TradingPairTrade,
+    labels=[
+        "id", 
+        "mts", 
+        "amount", 
+        "price"
+    ]
+)
+
+FundingCurrencyTrade = generate_labeler_serializer(
+    name="FundingCurrencyTrade",
+    klass=types.FundingCurrencyTrade,
+    labels=[
+        "id", 
+        "mts", 
+        "amount", 
+        "rate", 
+        "period"
+    ]
+)
+
+TradingPairBook = generate_labeler_serializer(
+    name="TradingPairBook",
+    klass=types.TradingPairBook,
+    labels=[
+        "price", 
+        "count", 
+        "amount"
+    ]
+)
+
+FundingCurrencyBook = generate_labeler_serializer(
+    name="FundingCurrencyBook",
+    klass=types.FundingCurrencyBook,
+    labels=[
+        "rate",
+        "period",
+        "count",
+        "amount"
+    ]
+)
+
+TradingPairRawBook = generate_labeler_serializer(
+    name="TradingPairRawBook",
+    klass=types.TradingPairRawBook,
+    labels=[
+        "order_id",
+        "price",
+        "amount"
+    ]
+)
+
+FundingCurrencyRawBook = generate_labeler_serializer(
+    name="FundingCurrencyRawBook",
+    klass=types.FundingCurrencyRawBook,
+    labels=[
+        "offer_id", 
+        "period", 
+        "rate", 
+        "amount"
+    ]
+)
+
+Candle = generate_labeler_serializer(
+    name="Candle",
+    klass=types.Candle,
+    labels=[
+        "mts", 
+        "open", 
+        "close", 
+        "high", 
+        "low", 
+        "volume"
+    ]
+)
+
+DerivativesStatus = generate_labeler_serializer(
+    name="DerivativesStatus",
+    klass=types.DerivativesStatus,
+    labels=[
+        "mts",
+        "_PLACEHOLDER", 
+        "deriv_price",
+        "spot_price",
+        "_PLACEHOLDER",
+        "insurance_fund_balance",
+        "_PLACEHOLDER",
+        "next_funding_evt_mts",
+        "next_funding_accrued",
+        "next_funding_step",
+        "_PLACEHOLDER",
+        "current_funding",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "mark_price",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "open_interest",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "clamp_min",
+        "clamp_max"
+    ]
+)
+
+#endregion
+
+#region Serializers definition for Websocket Authenticated Channels
+
+Order = generate_labeler_serializer(
+    name="Order",
+    klass=types.Order,
+    labels=[
+        "id",
+        "gid",
+        "cid",
+        "symbol",
+        "mts_create", 
+        "mts_update", 
+        "amount", 
+        "amount_orig", 
+        "order_type",
+        "type_prev",
+        "mts_tif",
+        "_PLACEHOLDER",
+        "flags",
+        "order_status",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "price",
+        "price_avg",
+        "price_trailing",
+        "price_aux_limit",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "notify",
+        "hidden", 
+        "placed_id",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "routing",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "meta"
+    ]
+)
+
+Position = generate_labeler_serializer(
+    name="Position",
+    klass=types.Position,
+    labels=[
+        "symbol", 
+        "status", 
+        "amount", 
+        "base_price", 
+        "margin_funding", 
+        "margin_funding_type",
+        "pl",
+        "pl_perc",
+        "price_liq",
+        "leverage",
+        "flag",
+        "position_id",
+        "mts_create",
+        "mts_update",
+        "_PLACEHOLDER",
+        "type",
+        "_PLACEHOLDER",
+        "collateral",
+        "collateral_min",
+        "meta"
+    ]
+)
+
+Trade = generate_labeler_serializer(
+    name="Trade",
+    klass=types.Trade,
+    labels=[
+        "id", 
+        "symbol", 
+        "mts_create",
+        "order_id", 
+        "exec_amount", 
+        "exec_price", 
+        "order_type", 
+        "order_price", 
+        "maker",
+        "fee",
+        "fee_currency",
+        "cid"
+    ]
+)
+
+FundingOffer = generate_labeler_serializer(
+    name="FundingOffer",
+    klass=types.FundingOffer,
+    labels=[
+        "id",
+        "symbol",
+        "mts_create",
+        "mts_update",
+        "amount",
+        "amount_orig",
+        "offer_type",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "flags",
+        "offer_status",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "rate",
+        "period",
+        "notify",
+        "hidden",
+        "_PLACEHOLDER",
+        "renew",
+        "_PLACEHOLDER"
+    ]
+)
+
+FundingCredit = generate_labeler_serializer(
+    name="FundingCredit",
+    klass=types.FundingCredit,
+    labels=[
+        "id",
+        "symbol",
+        "side",
+        "mts_create",
+        "mts_update",
+        "amount",
+        "flags",
+        "status",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "rate",
+        "period",
+        "mts_opening",
+        "mts_last_payout",
+        "notify",
+        "hidden",
+        "_PLACEHOLDER",
+        "renew",
+        "_PLACEHOLDER",
+        "no_close",
+        "position_pair"
+    ]
+)
+
+FundingLoan = generate_labeler_serializer(
+    name="FundingLoan",
+    klass=types.FundingLoan,
+    labels=[
+        "id",
+        "symbol",
+        "side",
+        "mts_create",
+        "mts_update",
+        "amount",
+        "flags",
+        "status",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "_PLACEHOLDER",
+        "rate",
+        "period",
+        "mts_opening",
+        "mts_last_payout",
+        "notify",
+        "hidden",
+        "_PLACEHOLDER",
+        "renew",
+        "_PLACEHOLDER",
+        "no_close"
+    ]
+)
+
+Wallet = generate_labeler_serializer(
+    name="Wallet",
+    klass=types.Wallet,
+    labels=[
+        "wallet_type", 
+        "currency", 
+        "balance", 
+        "unsettled_interest",
+        "available_balance",
+        "last_change",
+        "trade_details"
+    ]
+)
+
+Balance = generate_labeler_serializer(
+    name="Balance",
+    klass=types.Balance,
+    labels=[
+        "aum", 
+        "aum_net"
+    ]
+)
+
+#endregion
diff --git a/bfxapi/websocket/subscriptions.py b/bfxapi/websocket/subscriptions.py
new file mode 100644
index 00000000..922a532d
--- /dev/null
+++ b/bfxapi/websocket/subscriptions.py
@@ -0,0 +1,43 @@
+from typing import TypedDict, Union, Literal, Optional
+
+__all__ = [
+    "Subscription",
+
+    "Ticker",
+    "Trades",
+    "Book",
+    "Candles",
+    "Status"
+]
+
+_Header = TypedDict("_Header", { "event": Literal["subscribed"], "channel": str, "chanId": int })
+
+Subscription = Union["Ticker", "Trades", "Book", "Candles", "Status"]
+
+class Ticker(TypedDict):
+    subId: str
+    symbol: str
+    pair: Optional[str]
+    currency: Optional[str]
+
+class Trades(TypedDict):
+    subId: str
+    symbol: str
+    pair: Optional[str]
+    currency: Optional[str]
+
+class Book(TypedDict):
+    subId: str
+    symbol: str
+    prec: str
+    freq: str
+    len: str
+    pair: str
+
+class Candles(TypedDict):
+    subId: str
+    key: str
+
+class Status(TypedDict):
+    subId: str
+    key: str
diff --git a/bfxapi/websocket/types.py b/bfxapi/websocket/types.py
new file mode 100644
index 00000000..dcef21fc
--- /dev/null
+++ b/bfxapi/websocket/types.py
@@ -0,0 +1,247 @@
+#pylint: disable=duplicate-code
+
+#pylint: disable-next=wildcard-import,unused-wildcard-import
+from typing import *
+
+from dataclasses import dataclass
+
+from .. labeler import _Type
+
+#pylint: disable-next=unused-import
+from .. notification import Notification
+
+from ..utils.json_encoder import JSON
+
+#region Type hinting for Websocket Public Channels
+
+@dataclass
+class TradingPairTicker(_Type):
+    bid: float
+    bid_size: float
+    ask: float
+    ask_size: float
+    daily_change: float
+    daily_change_relative: float
+    last_price: float
+    volume: float
+    high: float
+    low: float
+
+@dataclass
+class FundingCurrencyTicker(_Type):
+    frr: float
+    bid: float
+    bid_period: int
+    bid_size: float
+    ask: float
+    ask_period: int
+    ask_size: float
+    daily_change: float
+    daily_change_relative: float
+    last_price: float
+    volume: float
+    high: float
+    low: float
+    frr_amount_available: float
+
+@dataclass
+class TradingPairTrade(_Type):
+    id: int
+    mts: int
+    amount: float
+    price: float
+
+@dataclass
+class FundingCurrencyTrade(_Type):
+    id: int
+    mts: int
+    amount: float
+    rate: float
+    period: int
+
+@dataclass
+class TradingPairBook(_Type):
+    price: float
+    count: int
+    amount: float
+
+@dataclass
+class FundingCurrencyBook(_Type):
+    rate: float
+    period: int
+    count: int
+    amount: float
+
+@dataclass
+class TradingPairRawBook(_Type):
+    order_id: int
+    price: float
+    amount: float
+
+@dataclass
+class FundingCurrencyRawBook(_Type):
+    offer_id: int
+    period: int
+    rate: float
+    amount: float
+
+@dataclass
+class Candle(_Type):
+    mts: int
+    open: int
+    close: int
+    high: int
+    low: int
+    volume: float
+
+@dataclass
+class DerivativesStatus(_Type):
+    mts: int
+    deriv_price: float
+    spot_price: float
+    insurance_fund_balance: float
+    next_funding_evt_mts: int
+    next_funding_accrued: float
+    next_funding_step: int
+    current_funding: float
+    mark_price: float
+    open_interest: float
+    clamp_min: float
+    clamp_max: float
+
+#endregion
+
+#region Type hinting for Websocket Authenticated Channels
+@dataclass
+class Order(_Type):
+    id: int
+    gid: int
+    cid: int
+    symbol: str
+    mts_create: int
+    mts_update: int
+    amount: float
+    amount_orig: float
+    order_type: str
+    type_prev: str
+    mts_tif: int
+    flags: int
+    order_status: str
+    price: float
+    price_avg: float
+    price_trailing: float
+    price_aux_limit: float
+    notify: int
+    hidden: int
+    placed_id: int
+    routing: str
+    meta: JSON
+
+@dataclass
+class Position(_Type):
+    symbol: str
+    status: str
+    amount: float
+    base_price: float
+    margin_funding: float
+    margin_funding_type: int
+    pl: float
+    pl_perc: float
+    price_liq: float
+    leverage: float
+    flag: int
+    position_id: int
+    mts_create: int
+    mts_update: int
+    type: int
+    collateral: float
+    collateral_min: float
+    meta: JSON
+
+@dataclass
+class Trade(_Type):
+    id: int
+    symbol: str
+    mts_create: int
+    order_id: int
+    exec_amount: float
+    exec_price: float
+    order_type: str
+    order_price: float
+    maker:int
+    fee: Optional[float]
+    fee_currency: Optional[str]
+    cid: int
+
+@dataclass
+class FundingOffer(_Type):
+    id: int
+    symbol: str
+    mts_create: int
+    mts_update: int
+    amount: float
+    amount_orig: float
+    offer_type: str
+    flags: int
+    offer_status: str
+    rate: float
+    period: int
+    notify: int
+    hidden: int
+    renew: int
+
+@dataclass
+class FundingCredit(_Type):
+    id: int
+    symbol: str
+    side: int
+    mts_create: int
+    mts_update: int
+    amount: float
+    flags: int
+    status: str
+    rate: float
+    period: int
+    mts_opening: int
+    mts_last_payout: int
+    notify: int
+    hidden: int
+    renew: int
+    no_close: int
+    position_pair: str
+
+@dataclass
+class FundingLoan(_Type):
+    id: int
+    symbol: str
+    side: int
+    mts_create: int
+    mts_update: int
+    amount: float
+    flags: int
+    status: str
+    rate: float
+    period: int
+    mts_opening: int
+    mts_last_payout: int
+    notify: int
+    hidden: int
+    renew: int
+    no_close: int
+
+@dataclass
+class Wallet(_Type):
+    wallet_type: str
+    currency: str
+    balance: float
+    unsettled_interest: float
+    available_balance: float
+    last_change: str
+    trade_details: JSON
+
+@dataclass
+class Balance(_Type):
+    aum: float
+    aum_net: float
+
+#endregion
diff --git a/bfxapi/websockets/__init__.py b/bfxapi/websockets/__init__.py
deleted file mode 100644
index a5701913..00000000
--- a/bfxapi/websockets/__init__.py
+++ /dev/null
@@ -1,1 +0,0 @@
-NAME = 'websockets'
diff --git a/bfxapi/websockets/bfx_websocket.py b/bfxapi/websockets/bfx_websocket.py
deleted file mode 100644
index 5554ec52..00000000
--- a/bfxapi/websockets/bfx_websocket.py
+++ /dev/null
@@ -1,790 +0,0 @@
-"""
-Module used to house the bitfine websocket client
-"""
-
-import asyncio
-import json
-import time
-import random
-
-from .generic_websocket import GenericWebsocket, AuthError
-from .subscription_manager import SubscriptionManager
-from .wallet_manager import WalletManager
-from .order_manager import OrderManager
-from ..utils.auth import generate_auth_payload
-from ..utils.decorators import handle_failure
-from ..models import Order, Trade, OrderBook, Ticker, FundingTicker
-from ..constants import PUB_WS_HOST
-
-
-class Flags:
-    """
-    Enum used to index the available flags used in the authentication
-    websocket packet
-    """
-    DEC_S = 9
-    TIME_S = 32
-    TIMESTAMP = 32768
-    SEQ_ALL = 65536
-    CHECKSUM = 131072
-
-    strings = {
-        9: 'DEC_S',
-        32: 'TIME_S',
-        32768: 'TIMESTAMP',
-        65536: 'SEQ_ALL',
-        131072: 'CHECKSUM'
-    }
-
-
-def _parse_candle(cData, symbol, tf):
-    return {
-        'mts': cData[0],
-        'open': cData[1],
-        'close': cData[2],
-        'high': cData[3],
-        'low': cData[4],
-        'volume': cData[5],
-        'symbol': symbol,
-        'tf': tf
-    }
-
-
-def _parse_trade_snapshot_item(tData, symbol):
-    return {
-        'mts': tData[3],
-        'price': tData[4],
-        'amount': tData[5],
-        'symbol': symbol
-    }
-
-
-def _parse_trade(tData, symbol):
-    return {
-        'id': tData[0],
-        'mts': tData[1],
-        'price': tData[3],
-        'amount': tData[2],
-        'symbol': symbol
-    }
-
-
-def _parse_user_trade(tData):
-    return {
-        'id': tData[0],
-        'symbol': tData[1],
-        'mts_create': tData[2],
-        'order_id': tData[3],
-        'exec_amount': tData[4],
-        'exec_price': tData[5],
-        'order_type': tData[6],
-        'order_price': tData[7],
-        'maker': tData[8],
-        'cid': tData[11],
-    }
-
-
-def _parse_user_trade_update(tData):
-    return {
-        'id': tData[0],
-        'symbol': tData[1],
-        'mts_create': tData[2],
-        'order_id': tData[3],
-        'exec_amount': tData[4],
-        'exec_price': tData[5],
-        'order_type': tData[6],
-        'order_price': tData[7],
-        'maker': tData[8],
-        'fee': tData[9],
-        'fee_currency': tData[10],
-        'cid': tData[11],
-    }
-
-
-def _parse_deriv_status_update(sData, symbol):
-    return {
-            'symbol': symbol,
-            'status_type': 'deriv',
-            'mts': sData[0],
-            # placeholder
-            'deriv_price': sData[2],
-            'spot_price': sData[3],
-            # placeholder
-            'insurance_fund_balance': sData[5],
-            # placeholder
-            # placeholder
-            'funding_accrued': sData[8],
-            'funding_step': sData[9],
-            # placeholder
-        }
-
-ERRORS = {
-    10000: 'Unknown event',
-    10001: 'Generic error',
-    10008: 'Concurrency error',
-    10020: 'Request parameters error',
-    10050: 'Configuration setup failed',
-    10100: 'Failed authentication',
-    10111: 'Error in authentication request payload',
-    10112: 'Error in authentication request signature',
-    10113: 'Error in authentication request encryption',
-    10114: 'Error in authentication request nonce',
-    10200: 'Error in un-authentication request',
-    10300: 'Subscription Failed (generic)',
-    10301: 'Already Subscribed',
-    10305: 'Reached limit of open channels',
-    10302: 'Unknown channel',
-    10400: 'Subscription Failed (generic)',
-    10401: 'Not subscribed',
-    11000: 'Not ready, try again later',
-    20000: 'User is invalid!',
-    20051: 'Websocket server stopping',
-    20060: 'Websocket server resyncing',
-    20061: 'Websocket server resync complete'
-}
-
-class BfxWebsocket(GenericWebsocket):
-    """
-    More complex websocket that heavily relies on the btfxwss module.
-    This websocket requires authentication and is capable of handling orders.
-    https://github.com/Crypto-toolbox/btfxwss
-
-    ### Emitter events:
-    - `all` (array|Object): listen for all messages coming through
-    - `connected:` () called when a connection is made
-    - `disconnected`: () called when a connection is ended (A reconnect attempt may follow)
-    - `stopped`: () called when max amount of connection retries is met and the socket is closed
-    - `authenticated` (): called when the websocket passes authentication
-    - `notification` (Notification): incoming account notification
-    - `error` (array): error from the websocket
-    - `order_closed` (Order, Trade): when an order has been closed
-    - `order_new` (Order, Trade): when an order has been created but not closed. Note: will not be called if order is executed and filled instantly
-    - `order_confirmed` (Order, Trade): When an order has been submitted and received
-    - `wallet_snapshot` (array[Wallet]): Initial wallet balances (Fired once)
-    - `order_snapshot` (array[Order]): Initial open orders (Fired once)
-    - `positions_snapshot` (array): Initial open positions (Fired once)
-    - `positions_new` (array): Initial open positions (Fired once)
-    - `positions_update` (array): An active position has been updated
-    - `positions_close` (array): An active position has closed
-    - `wallet_update` (Wallet): Changes to the balance of wallets
-    - `status_update` (Object): New platform status info
-    - `seed_candle` (Object): Initial past candle to prime strategy
-    - `seed_trade` (Object): Initial past trade to prime strategy
-    - `funding_offer_snapshot` (array): Opening funding offer balances
-    - `funding_loan_snapshot` (array): Opening funding loan balances
-    - `funding_credit_snapshot` (array): Opening funding credit balances
-    - `balance_update` (array): When the state of a balance is changed
-    - `new_trade` (array): A new trade on the market has been executed
-    - `new_user_trade` (array): A new - your - trade has been executed
-    - `new_ticker` (Ticker|FundingTicker): A new ticker update has been published
-    - `new_funding_ticker` (FundingTicker): A new funding ticker update has been published
-    - `new_trading_ticker` (Ticker): A new trading ticker update has been published
-    - `trade_update` (array): A trade on the market has been updated
-    - `new_candle` (array): A new candle has been produced
-    - `margin_info_updates` (array): New margin information has been broadcasted
-    - `funding_info_updates` (array): New funding information has been broadcasted
-    - `order_book_snapshot` (array): Initial snapshot of the order book on connection
-    - `order_book_update` (array): A new order has been placed into the ordebrook
-    - `subscribed` (Subscription): A new channel has been subscribed to
-    - `unsubscribed` (Subscription): A channel has been un-subscribed
-    """
-
-    def __init__(self, API_KEY=None, API_SECRET=None, host=PUB_WS_HOST,
-                 manageOrderBooks=False, dead_man_switch=False, ws_capacity=25, logLevel='INFO', parse_float=float,
-                 channel_filter=[], *args, **kwargs):
-        self.API_KEY = API_KEY
-        self.API_SECRET = API_SECRET
-        self.manageOrderBooks = manageOrderBooks
-        self.dead_man_switch = dead_man_switch
-        self.orderBooks = {}
-        self.ws_capacity = ws_capacity
-        self.channel_filter = channel_filter
-        # How should we store float values? could also be bfxapi.decimal
-        # which is slower but has higher precision.
-        self.parse_float = parse_float
-        super(BfxWebsocket, self).__init__(host, logLevel=logLevel, *args, **kwargs)
-        self.subscriptionManager = SubscriptionManager(self, logLevel=logLevel)
-        self.orderManager = OrderManager(self, logLevel=logLevel)
-        self.wallets = WalletManager()
-
-        self._WS_DATA_HANDLERS = {
-            'tu': self._trade_update_handler,
-            'wu': self._wallet_update_handler,
-            'hb': self._heart_beat_handler,
-            'te': self._trade_executed_handler,
-            'oc': self._order_closed_handler,
-            'ou': self._order_update_handler,
-            'on': self._order_new_handler,
-            'os': self._order_snapshot_handler,
-            'ws': self._wallet_snapshot_handler,
-            'ps': self._position_snapshot_handler,
-            'pu': self._position_update_handler,
-            'pn': self._position_new_handler,
-            'pc': self._position_close_handler,
-            'fos': self._funding_offer_snapshot_handler,
-            'fcs': self._funding_credit_snapshot_handler,
-            'fls': self._funding_load_snapshot_handler,
-            'bu': self._balance_update_handler,
-            'n': self._notification_handler,
-            'miu': self._margin_info_update_handler,
-            'fiu': self._funding_info_update_handler
-        }
-
-        self._WS_SYSTEM_HANDLERS = {
-            'info': self._system_info_handler,
-            'subscribed': self._system_subscribed_handler,
-            'unsubscribed': self._system_unsubscribe_handler,
-            'error': self._system_error_handler,
-            'auth': self._system_auth_handler,
-            'conf': self._system_conf_handler
-        }
-
-    async def _ws_system_handler(self, socketId, msg):
-        eType = msg.get('event')
-        if eType in self._WS_SYSTEM_HANDLERS:
-            await self._WS_SYSTEM_HANDLERS[eType](socketId, msg)
-        else:
-            self.logger.warn(
-                "Unknown websocket event (socketId={}): '{}' {}".format(socketId, eType, msg))
-
-    async def _ws_data_handler(self, socketId, data, raw_message_str):
-        dataEvent = data[1]
-        chan_id = data[0]
-
-        if type(dataEvent) is str and dataEvent in self._WS_DATA_HANDLERS:
-            return await self._WS_DATA_HANDLERS[dataEvent](data)
-        elif self.subscriptionManager.is_subscribed(chan_id):
-            subscription = self.subscriptionManager.get(chan_id)
-            # candles do not have an event
-            if subscription.channel_name == 'candles':
-                await self._candle_handler(data)
-            elif subscription.channel_name == 'book':
-                await self._order_book_handler(data, raw_message_str)
-            elif subscription.channel_name == 'trades':
-                await self._trade_handler(data)
-            elif subscription.channel_name == 'status':
-                await self._status_handler(data)
-            elif subscription.channel_name == 'ticker':
-                await self._ticker_handler(data)
-            else:
-                self.logger.warn("Unknown channel type '{}'".format(subscription.channel_name))
-        else:
-            self.logger.warn(
-                "Unknown data event: '{}' {}".format(dataEvent, data))
-
-    async def _system_info_handler(self, socketId, data):
-        self.logger.info(data)
-        if data.get('serverId', None):
-            # connection has been established
-            await self.on_open(socketId)
-
-    async def _system_conf_handler(self, socketId, data):
-        flag = data.get('flags')
-        status = data.get('status')
-        if flag not in Flags.strings:
-            self.logger.warn("Unknown config value set {}".format(flag))
-            return
-        flagString = Flags.strings[flag]
-        if status == "OK":
-            self.logger.info("Enabled config flag {}".format(flagString))
-        else:
-            self.logger.error(
-                "Unable to enable config flag {}".format(flagString))
-
-    async def _system_subscribed_handler(self, socket_id, data):
-        await self.subscriptionManager.confirm_subscription(socket_id, data)
-
-    async def _system_unsubscribe_handler(self, socket_id, data):
-        await self.subscriptionManager.confirm_unsubscribe(socket_id, data)
-
-    async def _system_error_handler(self, socketId, data):
-        err_string = ERRORS[data.get('code', 10000)]
-        err_string = "(socketId={}) {} - {}".format(
-            socketId,
-            ERRORS[data.get('code', 10000)],
-            data.get("msg", ""))
-        self._emit(Exception(err_string))
-
-    async def _system_auth_handler(self, socketId, data):
-        if data.get('status') == 'FAILED':
-            raise AuthError(ERRORS[data.get('code')])
-        else:
-            self._emit('authenticated', data)
-            self.logger.info("Authentication successful.")
-
-    async def _trade_update_handler(self, data):
-        tData = data[2]
-        # [209, 'tu', [312372989, 1542303108930, 0.35, 5688.61834032]]
-        if self.subscriptionManager.is_subscribed(data[0]):
-            symbol = self.subscriptionManager.get(data[0]).symbol
-            tradeObj = _parse_trade(tData, symbol)
-            self._emit('trade_update', tradeObj)
-        else:
-            # user trade
-            # [0,"tu",[738045455,"tTESTBTC:TESTUSD",1622169615771,66635385225,0.001,38175,"EXCHANGE LIMIT",39000,-1,-0.000002,"TESTBTC",1622169615685]]
-            tradeObj = _parse_user_trade_update(tData)
-            self._emit('user_trade_update', tradeObj)
-
-    async def _trade_executed_handler(self, data):
-        tData = data[2]
-        # [209, 'te', [312372989, 1542303108930, 0.35, 5688.61834032]]
-        if self.subscriptionManager.is_subscribed(data[0]):
-            symbol = self.subscriptionManager.get(data[0]).symbol
-            tradeObj = _parse_trade(tData, symbol)
-            self._emit('new_trade', tradeObj)
-        else:
-            # user trade
-            # [0, 'te', [37558151, 'tBTCUSD', 1643542688513, 1512164914, 0.0001, 30363, 'EXCHANGE MARKET', 100000, -1, None, None, 1643542688390]]
-            tradeObj = _parse_user_trade(tData)
-            self._emit('new_user_trade', tradeObj)
-
-    async def _wallet_update_handler(self, data):
-        # [0,"wu",["exchange","USD",89134.66933283,0]]
-        uw = self.wallets._update_from_event(data)
-        self._emit('wallet_update', uw)
-        self.logger.info("Wallet update: {}".format(uw))
-
-    async def _heart_beat_handler(self, data):
-        self.logger.debug("Heartbeat - {}".format(self.host))
-
-    async def _margin_info_update_handler(self, data):
-        self._emit('margin_info_update', data)
-        self.logger.info("Margin info update: {}".format(data))
-
-    async def _funding_info_update_handler(self, data):
-        self._emit('funding_info_update', data)
-        self.logger.info("Funding info update: {}".format(data))
-
-    async def _notification_handler(self, data):
-        nInfo = data[2]
-        self._emit('notification', nInfo)
-        notificationType = nInfo[6]
-        notificationText = nInfo[7]
-        if notificationType == 'ERROR':
-            # self._emit('error', notificationText)
-            await self._order_error_handler(data)
-            self.logger.error(
-                "Notification ERROR: {}".format(notificationText))
-        else:
-            self.logger.info(
-                "Notification SUCCESS: {}".format(notificationText))
-
-    async def _balance_update_handler(self, data):
-        self.logger.info('Balance update: {}'.format(data[2]))
-        self._emit('balance_update', data[2])
-
-    async def _order_closed_handler(self, data):
-        await self.orderManager.confirm_order_closed(data)
-
-    async def _order_error_handler(self, data):
-        await self.orderManager.confirm_order_error(data)
-
-    async def _order_update_handler(self, data):
-        await self.orderManager.confirm_order_update(data)
-
-    async def _order_new_handler(self, data):
-        await self.orderManager.confirm_order_new(data)
-
-    async def _order_snapshot_handler(self, data):
-        await self.orderManager.build_from_order_snapshot(data)
-
-    async def _wallet_snapshot_handler(self, data):
-        wallets = self.wallets._update_from_snapshot(data)
-        self._emit('wallet_snapshot', wallets)
-
-    async def _position_snapshot_handler(self, data):
-        self._emit('position_snapshot', data)
-        self.logger.info("Position snapshot: {}".format(data))
-
-    async def _position_update_handler(self, data):
-        self._emit('position_update', data)
-        self.logger.info("Position update: {}".format(data))
-
-    async def _position_close_handler(self, data):
-        self._emit('position_close', data)
-        self.logger.info("Position close: {}".format(data))
-
-    async def _position_new_handler(self, data):
-        self._emit('position_new', data)
-        self.logger.info("Position new: {}".format(data))
-
-    async def _funding_offer_snapshot_handler(self, data):
-        self._emit('funding_offer_snapshot', data)
-        self.logger.info("Funding offer snapshot: {}".format(data))
-
-    async def _funding_load_snapshot_handler(self, data):
-        self._emit('funding_loan_snapshot', data[2])
-        self.logger.info("Funding loan snapshot: {}".format(data))
-
-    async def _funding_credit_snapshot_handler(self, data):
-        self._emit('funding_credit_snapshot', data[2])
-        self.logger.info("Funding credit snapshot: {}".format(data))
-
-    async def _status_handler(self, data):
-        sub = self.subscriptionManager.get(data[0])
-        symbol = sub.symbol
-        status_type = sub.key.split(":")[0]
-        rstatus = data[1]
-        if status_type == "deriv":
-            status = _parse_deriv_status_update(rstatus, symbol)
-        if status:
-            self._emit('status_update', status)
-        else:
-            self.logger.warn('Unknown status data type: {}'.format(data))
-
-    async def _ticker_handler(self, data):
-        symbol = self.subscriptionManager.get(data[0]).symbol
-        if type(data[1]) is list and len(symbol) > 0:
-            raw_ticker = data[1]
-            t = None
-            if symbol[0] == 't':
-                t = Ticker.from_raw_ticker(raw_ticker, symbol)
-                self._emit('new_trading_ticker', t)
-            elif symbol[0] == 'f':
-                t = FundingTicker.from_raw_ticker(raw_ticker, symbol)
-                self._emit('new_funding_ticker', t)
-            else:
-                self.logger.warn('Unknown ticker type: {}'.format(raw_ticker))
-            self._emit('new_ticker', t)
-
-    async def _trade_handler(self, data):
-        symbol = self.subscriptionManager.get(data[0]).symbol
-        if type(data[1]) is list:
-            data = data[1]
-            # Process the batch of seed trades on
-            # connection
-            data.reverse()
-            for t in data:
-                trade = {
-                    'mts': t[1],
-                    'amount': t[2],
-                    'price': t[3],
-                    'symbol': symbol
-                }
-                self._emit('seed_trade', trade)
-
-    async def _candle_handler(self, data):
-        subscription = self.subscriptionManager.get(data[0])
-        # if candle data is empty
-        if data[1] == []:
-            return
-        if type(data[1][0]) is list:
-            # Process the batch of seed candles on
-            # websocket subscription
-            candlesSnapshot = data[1]
-            candlesSnapshot.reverse()
-            for c in candlesSnapshot:
-                candle = _parse_candle(
-                    c, subscription.symbol, subscription.timeframe)
-                self._emit('seed_candle', candle)
-        else:
-            candle = _parse_candle(
-                data[1], subscription.symbol, subscription.timeframe)
-            self._emit('new_candle', candle)
-
-    async def _order_book_handler(self, data, orig_raw_message):
-        obInfo = data[1]
-        chan_id = data[0]
-        subscription = self.subscriptionManager.get(data[0])
-        symbol = subscription.symbol
-        if data[1] == "cs":
-            dChecksum = data[2] & 0xffffffff  # force to signed int
-            checksum = self.orderBooks[symbol].checksum()
-            # force checksums to signed integers
-            isValid = (dChecksum) == (checksum)
-            if isValid:
-                msg = "Checksum orderbook validation for '{}' successful."
-                self.logger.debug(msg.format(symbol))
-            else:
-                msg = "Checksum orderbook invalid for '{}'. Resetting subscription."
-                self.logger.warn(msg.format(symbol))
-                # re-build orderbook with snapshot
-                await self.subscriptionManager.resubscribe(chan_id)
-            return
-        if obInfo == []:
-            self.orderBooks[symbol] = OrderBook()
-            return
-        isSnapshot = type(obInfo[0]) is list
-        if isSnapshot:
-            self.orderBooks[symbol] = OrderBook()
-            self.orderBooks[symbol].update_from_snapshot(obInfo, orig_raw_message)
-            self._emit('order_book_snapshot', {
-                       'symbol': symbol, 'data': obInfo})
-        else:
-            self.orderBooks[symbol].update_with(obInfo, orig_raw_message)
-            self._emit('order_book_update', {'symbol': symbol, 'data': obInfo})
-
-    async def on_message(self, socketId, message):
-        self.logger.debug(message)
-        # convert float values to decimal
-        msg = json.loads(message, parse_float=self.parse_float)
-        self._emit('all', msg)
-        if type(msg) is dict:
-            # System messages are received as json
-            await self._ws_system_handler(socketId, msg)
-        elif type(msg) is list:
-            # All data messages are received as a list
-            await self._ws_data_handler(socketId, msg, message)
-        else:
-            self.logger.warn('Unknown (socketId={}) websocket response: {}'.format(socketId, msg))
-
-    @handle_failure
-    async def _ws_authenticate_socket(self, socketId):
-        socket = self.sockets[socketId]
-        socket.set_authenticated()
-        jdata = generate_auth_payload(self.API_KEY, self.API_SECRET)
-        if self.dead_man_switch:
-            jdata['dms'] = 4
-        if len(self.channel_filter) > 0:
-            jdata['filter'] = self.channel_filter
-        await socket.send(json.dumps(jdata))
-
-    async def on_open(self, socket_id):
-        self.logger.info("Websocket opened.")
-        if len(self.sockets) == 1:
-            ## only call on first connection
-            self._emit('connected')
-        # Orders are simulated in backtest mode
-        if self.API_KEY and self.API_SECRET and self.get_authenticated_socket() == None:
-            await self._ws_authenticate_socket(socket_id)
-        # enable order book checksums
-        if self.manageOrderBooks:
-            await self.enable_flag(Flags.CHECKSUM)
-        # set any existing subscriptions to not subscribed
-        self.subscriptionManager.set_unsubscribed_by_socket(socket_id)
-        # re-subscribe to existing channels
-        await self.subscriptionManager.resubscribe_by_socket(socket_id)
-
-    @handle_failure
-    async def _send_auth_command(self, channel_name, data):
-        payload = [0, channel_name, None, data]
-        socket = self.get_authenticated_socket()
-        if socket == None:
-            raise ValueError("authenticated socket connection not found")
-        if not socket.isConnected:
-            raise ValueError("authenticated socket not connected")
-        await socket.send(json.dumps(payload))
-
-    def get_orderbook(self, symbol):
-        return self.orderBooks.get(symbol, None)
-
-    def get_socket_capacity(self, socket_id):
-        return self.ws_capacity - self.subscriptionManager.get_sub_count_by_socket(socket_id)
-
-    def get_most_available_socket(self):
-        bestId = None
-        bestCount = 0
-        for socketId in self.sockets:
-            cap = self.get_socket_capacity(socketId)
-            if bestId == None or cap > bestCount:
-                bestId = socketId
-                bestCount = cap
-        return self.sockets[socketId]
-
-    def get_total_available_capcity(self):
-        total = 0
-        for socketId in self.sockets:
-            total += self.get_socket_capacity(socketId)
-        return total
-
-    @handle_failure
-    async def enable_flag(self, flag):
-        """
-        Enable flag on websocket connection
-
-        # Attributes
-        flag (int): int flag value
-        """
-        payload = {
-            "event": 'conf',
-            "flags": flag
-        }
-        # enable on all sockets
-        for socket in self.sockets.values():
-            if socket.isConnected:
-                await socket.send(json.dumps(payload))
-
-    async def subscribe_order_book(self, symbol):
-        """
-        Subscribe to an orderbook data feed
-
-        # Attributes
-        @param symbol: the trading symbol i.e 'tBTCUSD'
-        """
-        return await self.subscribe('book', symbol)
-
-    async def subscribe_candles(self, symbol, timeframe):
-        """
-        Subscribe to a candle data feed
-
-        # Attributes
-        @param symbol: the trading symbol i.e 'tBTCUSD'
-        @param timeframe: resolution of the candle i.e 15m, 1h
-        """
-        return await self.subscribe('candles', symbol, timeframe=timeframe)
-
-    async def subscribe_trades(self, symbol):
-        """
-        Subscribe to a trades data feed
-
-        # Attributes
-        @param symbol: the trading symbol i.e 'tBTCUSD'
-        """
-        return await self.subscribe('trades', symbol)
-
-    async def subscribe_ticker(self, symbol):
-        """
-        Subscribe to a ticker data feed
-
-        # Attributes
-        @param symbol: the trading symbol i.e 'tBTCUSD'
-        """
-        return await self.subscribe('ticker', symbol)
-
-    async def subscribe_derivative_status(self, symbol):
-        """
-        Subscribe to a status data feed
-
-        # Attributes
-        @param symbol: the trading symbol i.e 'tBTCUSD'
-        """
-        key = 'deriv:{}'.format(symbol)
-        return await self.subscribe('status', symbol, key=key)
-
-    async def subscribe(self, *args, **kwargs):
-        """
-        Subscribe to a new channel
-
-        # Attributes
-        @param channel_name: the name of the channel i.e 'books', 'candles'
-        @param symbol: the trading symbol i.e 'tBTCUSD'
-        @param timeframe: sepecifies the data timeframe between each candle (only required
-          for the candles channel)
-        """
-        return await self.subscriptionManager.subscribe(*args, **kwargs)
-
-    async def unsubscribe(self, *args, **kwargs):
-        """
-        Unsubscribe from the channel with the given chanId
-
-        # Attributes
-        @param onComplete: function called when the bitfinex websocket responds with
-          a signal that confirms the subscription has been unsubscribed to
-        """
-        return await self.subscriptionManager.unsubscribe(*args, **kwargs)
-
-    async def resubscribe(self, *args, **kwargs):
-        """
-        Unsubscribes and then subscribes to the channel with the given Id
-
-        This function is mostly used to force the channel to produce a fresh snapshot.
-        """
-        return await self.subscriptionManager.resubscribe(*args, **kwargs)
-
-    async def unsubscribe_all(self, *args, **kwargs):
-        """
-        Unsubscribe from all channels.
-        """
-        return await self.subscriptionManager.unsubscribe_all(*args, **kwargs)
-
-    async def resubscribe_all(self, *args, **kwargs):
-        """
-        Unsubscribe and then subscribe to all channels
-        """
-        return await self.subscriptionManager.resubscribe_all(*args, **kwargs)
-
-    async def submit_order(self, *args, **kwargs):
-        """
-        Submit a new order
-
-        # Attributes
-        @param gid: assign the order to a group identifier
-        @param symbol: the name of the symbol i.e 'tBTCUSD
-        @param price: the price you want to buy/sell at (must be positive)
-        @param amount: order size: how much you want to buy/sell,
-          a negative amount indicates a sell order and positive a buy order
-        @param market_type	Order.Type: please see Order.Type enum
-          amount	decimal string	Positive for buy, Negative for sell
-        @param hidden: if True, order should be hidden from orderbooks
-        @param price_trailing:	decimal trailing price
-        @param price_aux_limit:	decimal	auxiliary Limit price (only for STOP LIMIT)
-        @param oco_stop_price: set the oco stop price (requires oco = True)
-        @param close: if True, close position if position present
-        @param reduce_only: if True, ensures that the executed order does not flip the opened position
-        @param post_only: if True, ensures the limit order will be added to the order book and not
-          match with a pre-existing order
-        @param oco: cancels other order option allows you to place a pair of orders stipulating
-          that if one order is executed fully or partially, then the other is automatically canceled
-
-        @param time_in_force:	datetime for automatic order cancellation ie. 2020-01-01 10:45:23
-        @param leverage: the amount of leverage to apply to the order as an integer
-        @param onConfirm: function called when the bitfinex websocket receives signal that the order
-          was confirmed
-        @param onClose: function called when the bitfinex websocket receives signal that the order
-          was closed due to being filled or cancelled
-        """
-        return await self.orderManager.submit_order(*args, **kwargs)
-
-    async def update_order(self, *args, **kwargs):
-        """
-        Update an existing order
-
-        # Attributes
-        @param orderId: the id of the order that you want to update
-        @param price: the price you want to buy/sell at (must be positive)
-        @param amount: order size: how much you want to buy/sell,
-          a negative amount indicates a sell order and positive a buy order
-        @param delta:	change of amount
-        @param price_trailing:	decimal trailing price
-        @param price_aux_limit:	decimal	auxiliary Limit price (only for STOP LIMIT)
-        @param hidden: if True, order should be hidden from orderbooks
-        @param close: if True, close position if position present
-        @param reduce_only: if True, ensures that the executed order does not flip the opened position
-        @param post_only: if True, ensures the limit order will be added to the order book and not
-          match with a pre-existing order
-        @param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23
-        @param leverage: the amount of leverage to apply to the order as an integer
-        @param onConfirm: function called when the bitfinex websocket receives signal that the order
-          was confirmed
-        @param onClose: function called when the bitfinex websocket receives signal that the order
-          was closed due to being filled or cancelled
-        """
-        return await self.orderManager.update_order(*args, **kwargs)
-
-    async def cancel_order(self, *args, **kwargs):
-        """
-        Cancel an existing open order
-
-        # Attributes
-        @param orderId: the id of the order that you want to update
-        @param onConfirm: function called when the bitfinex websocket receives signal that the
-                          order
-          was confirmed
-        @param onClose: function called when the bitfinex websocket receives signal that the order
-          was closed due to being filled or cancelled
-        """
-        return await self.orderManager.cancel_order(*args, **kwargs)
-
-    async def cancel_order_group(self, *args, **kwargs):
-        """
-        Cancel a set of orders using a single group id.
-        """
-        return await self.orderManager.cancel_order_group(*args, **kwargs)
-
-    async def cancel_all_orders(self, *args, **kwargs):
-        """
-        Cancel all existing open orders
-
-        This function closes all open orders.
-        """
-        return await self.orderManager.cancel_all_orders(*args, **kwargs)
-
-    async def cancel_order_multi(self, *args, **kwargs):
-        """
-        Cancel existing open orders as a batch
-
-        # Attributes
-        @param ids: an array of order ids
-        @param gids: an array of group ids
-        """
-        return await self.orderManager.cancel_order_multi(*args, **kwargs)
diff --git a/bfxapi/websockets/generic_websocket.py b/bfxapi/websockets/generic_websocket.py
deleted file mode 100644
index 3530ff4f..00000000
--- a/bfxapi/websockets/generic_websocket.py
+++ /dev/null
@@ -1,223 +0,0 @@
-"""
-Module used as a interfeace to describe a generick websocket client
-"""
-
-import asyncio
-import websockets
-import socket
-import json
-import time
-from threading import Thread, Lock
-
-from pyee import AsyncIOEventEmitter
-from ..utils.custom_logger import CustomLogger
-
-# websocket exceptions
-from websockets.exceptions import ConnectionClosed, InvalidStatusCode
-
-class AuthError(Exception):
-    """
-    Thrown whenever there is a problem with the authentication packet
-    """
-    pass
-
-def is_json(myjson):
-    try:
-        json_object = json.loads(myjson)
-    except ValueError as e:
-        return False
-    return True
-
-class Socket():
-    def __init__(self, sId):
-        self.ws = None
-        self.isConnected = False
-        self.isAuthenticated = False
-        self.id = sId
-        self.lock = Lock()
-
-    def set_connected(self):
-        self.isConnected = True
-
-    def set_disconnected(self):
-        self.isConnected = False
-
-    def set_authenticated(self):
-        self.isAuthenticated = True
-
-    def set_unauthenticated(self):
-        self.isAuthenticated = False
-
-    def set_websocket(self, ws):
-        self.ws = ws
-
-    async def send(self, data):
-        with self.lock:
-            await self.ws.send(data)
-
-def _start_event_worker():
-    return AsyncIOEventEmitter()
-
-class GenericWebsocket:
-    """
-    Websocket object used to contain the base functionality of a websocket.
-    Inlcudes an event emitter and a standard websocket client.
-    """
-    logger = CustomLogger('BfxWebsocket', logLevel="DEBUG")
-
-    def __init__(self, host, logLevel='INFO', max_retries=5, create_event_emitter=None):
-        self.host = host
-        self.logger.set_level(logLevel)
-        # overide 'error' event to stop it raising an exception
-        # self.events.on('error', self.on_error)
-        self.ws = None
-        self.max_retries = max_retries
-        self.attempt_retry = True
-        self.sockets = {}
-        # start separate process for the even emitter
-        create_ee = create_event_emitter or _start_event_worker
-        self.events = create_ee()
-
-    def run(self):
-        """
-        Start the websocket connection. This functions spawns the initial socket
-        thread and connection.
-        """
-        self._start_new_socket()
-        event_loop = asyncio.get_event_loop()
-        if not event_loop or not event_loop.is_running():
-            while True:
-                time.sleep(1)
-
-    def get_task_executable(self):
-        """
-        Get the run indefinitely asyncio task
-        """
-        return self._run_socket()
-
-    def _start_new_async_socket(self):
-        loop = asyncio.new_event_loop()
-        loop.run_until_complete(self._run_socket())
-
-    def _start_new_socket(self, socketId=None):
-        if not socketId:
-            socketId = len(self.sockets)
-        worker = Thread(target=self._start_new_async_socket)
-        worker.start()
-        return socketId
-
-    def _wait_for_socket(self, socket_id):
-        """
-        Block until the given socket connection is open
-        """
-        while True:
-            socket = self.sockets.get(socket_id, False)
-            if socket:
-                if socket.isConnected and socket.ws:
-                    return
-            time.sleep(0.01)
-
-    def get_socket(self, socketId):
-        return self.sockets[socketId]
-
-    def get_authenticated_socket(self):
-        for socketId in self.sockets:
-            if self.sockets[socketId].isAuthenticated:
-                return self.sockets[socketId]
-        return None
-
-    async def _run_socket(self):
-        retries = 0
-        sId =  len(self.sockets)
-        s = Socket(sId)
-        self.sockets[sId] = s
-        loop = asyncio.get_event_loop()
-        while self.max_retries == 0 or (retries < self.max_retries and self.attempt_retry):
-            try:
-                async with websockets.connect(self.host) as websocket:
-                    self.sockets[sId].set_websocket(websocket)
-                    self.sockets[sId].set_connected()
-                    self.logger.info("Websocket connected to {}".format(self.host))
-                    retries = 0
-                    while True:
-                        # optimization - wait 0 seconds to force the async queue
-                        # to be cleared before continuing
-                        await asyncio.sleep(0)
-                        message = await websocket.recv()
-                        await self.on_message(sId, message)
-            except (ConnectionClosed, socket.error, InvalidStatusCode) as e:
-                self.sockets[sId].set_disconnected()
-                if self.sockets[sId].isAuthenticated:
-                    self.sockets[sId].set_unauthenticated()
-                self._emit('disconnected')
-                if (not self.attempt_retry):
-                    return
-                self.logger.error(str(e))
-                retries += 1
-                # wait 5 seconds befor retrying
-                self.logger.info("Waiting 5 seconds before retrying...")
-                await asyncio.sleep(5)
-                self.logger.info("Reconnect attempt {}/{}".format(retries, self.max_retries))
-        self.logger.info("Unable to connect to websocket.")
-        self._emit('stopped')
-
-    async def stop(self):
-        """
-        Stop all websocket connections
-        """
-        self.attempt_retry = False
-        for key, socket in self.sockets.items():
-            await socket.ws.close()
-        self._emit('done')
-
-    def remove_all_listeners(self, event):
-        """
-        Remove all listeners from event emitter
-        """
-        self.events.remove_all_listeners(event)
-
-    def on(self, event, func=None):
-        """
-        Add a new event to the event emitter
-        """
-        if not func:
-            return self.events.on(event)
-        self.events.on(event, func)
-
-    def once(self, event, func=None):
-        """
-        Add a new event to only fire once to the event
-        emitter
-        """
-        if not func:
-            return self.events.once(event)
-        self.events.once(event, func)
-
-    def _emit(self, event, *args, **kwargs):
-        if type(event) == Exception:
-            self.logger.error(event)
-        self.events.emit(event, *args, **kwargs)
-
-    async def on_error(self, error):
-        """
-        On websocket error print and fire event
-        """
-        self.logger.error(error)
-
-    async def on_close(self):
-        """
-        This is used by the HF data server.
-        """
-        await self.stop()
-
-    async def on_open(self):
-        """
-        On websocket open
-        """
-        pass
-
-    async def on_message(self, message):
-        """
-        On websocket message
-        """
-        pass
diff --git a/bfxapi/websockets/order_manager.py b/bfxapi/websockets/order_manager.py
deleted file mode 100644
index 91ab04ba..00000000
--- a/bfxapi/websockets/order_manager.py
+++ /dev/null
@@ -1,280 +0,0 @@
-"""
-Module used to house all of the functions/classes used to handle orders
-"""
-
-import time
-import asyncio
-
-from ..utils.custom_logger import CustomLogger
-from ..models import Order
-from ..utils.auth import calculate_order_flags, gen_unique_cid
-
-
-class OrderManager:
-    """
-    Handles all of the functionality for opening, updating and closing order.
-    Also contains state such as all of your open orders and orders that have
-    closed.
-    """
-
-    def __init__(self, bfxapi, logLevel='INFO'):
-        self.bfxapi = bfxapi
-        self.pending_orders = {}
-        self.closed_orders = {}
-        self.open_orders = {}
-
-        self.pending_order_close_callbacks = {}
-        self.pending_order_confirm_callbacks = {}
-        self.pending_update_confirm_callbacks = {}
-        self.pending_cancel_confirm_callbacks = {}
-        self.logger = CustomLogger('BfxOrderManager', logLevel=logLevel)
-
-    def get_open_orders(self):
-        return list(self.open_orders.values())
-
-    def get_closed_orders(self):
-        return list(self.closed_orders.values())
-
-    def get_pending_orders(self):
-        return list(self.pending_orders.values())
-
-    async def confirm_order_closed(self, raw_ws_data):
-        order = Order.from_raw_order(raw_ws_data[2])
-        order.set_open_state(False)
-        if order.id in self.open_orders:
-            del self.open_orders[order.id]
-        if order.cid in self.pending_orders:
-            del self.pending_orders[order.cid]
-        self.closed_orders[order.id] = order
-        if not order.is_confirmed():
-            order.set_confirmed()
-            self.bfxapi._emit('order_confirmed', order)
-        await self._execute_callback(order, self.pending_order_confirm_callbacks)
-        await self._execute_callback(order, self.pending_cancel_confirm_callbacks)
-        await self._execute_callback(order, self.pending_update_confirm_callbacks)
-        await self._execute_callback(order, self.pending_order_close_callbacks)
-        self.logger.info("Order closed: {} {}".format(
-            order.symbol, order.status))
-        self.bfxapi._emit('order_closed', order)
-
-    async def build_from_order_snapshot(self, raw_ws_data):
-        """
-        Rebuild the user orderbook based on an incoming snapshot
-        """
-        osData = raw_ws_data[2]
-        self.open_orders = {}
-        for raw_order in osData:
-            order = Order.from_raw_order(raw_order)
-            order.set_open_state(True)
-            self.open_orders[order.id] = order
-        self.bfxapi._emit('order_snapshot', self.get_open_orders())
-
-    async def confirm_order_update(self, raw_ws_data):
-        order = Order.from_raw_order(raw_ws_data[2])
-        order.set_open_state(True)
-        self.open_orders[order.id] = order
-        await self._execute_callback(order, self.pending_update_confirm_callbacks)
-        self.logger.info("Order update: {}".format(order))
-        self.bfxapi._emit('order_update', order)
-
-    async def confirm_order_new(self, raw_ws_data):
-        order = Order.from_raw_order(raw_ws_data[2])
-        order.set_open_state(True)
-        if order.cid in self.pending_orders:
-            del self.pending_orders[order.cid]
-        self.open_orders[order.id] = order
-        order.set_confirmed()
-        self.bfxapi._emit('order_confirmed', order)
-        await self._execute_callback(order, self.pending_order_confirm_callbacks)
-        self.logger.info("Order new: {}".format(order))
-        self.bfxapi._emit('order_new', order)
-
-    async def confirm_order_error(self, raw_ws_data):
-        cid = raw_ws_data[2][4][2]
-        if cid in self.pending_orders:
-            del self.pending_orders[cid]
-        self.logger.info("Deleted Order CID {} from pending orders".format(cid))
-
-    async def submit_order(self, symbol, price, amount, market_type=Order.Type.LIMIT,
-                           hidden=False, price_trailing=None, price_aux_limit=None,
-                           oco_stop_price=None, close=False, reduce_only=False,
-                           post_only=False, oco=False, aff_code=None, time_in_force=None,
-                           leverage=None, onConfirm=None, onClose=None, gid=None, *args, **kwargs):
-        """
-        Submit a new order
-
-        @param gid: assign the order to a group identifier
-        @param symbol: the name of the symbol i.e 'tBTCUSD
-        @param price: the price you want to buy/sell at (must be positive)
-        @param amount: order size: how much you want to buy/sell,
-          a negative amount indicates a sell order and positive a buy order
-        @param market_type	Order.Type: please see Order.Type enum
-          amount	decimal string	Positive for buy, Negative for sell
-        @param hidden: if True, order should be hidden from orderbooks
-        @param price_trailing:	decimal trailing price
-        @param price_aux_limit:	decimal	auxiliary Limit price (only for STOP LIMIT)
-        @param oco_stop_price: set the oco stop price (requires oco = True)
-        @param close: if True, close position if position present
-        @param reduce_only: if True, ensures that the executed order does not flip the opened position
-        @param post_only: if True, ensures the limit order will be added to the order book and not
-          match with a pre-existing order
-        @param oco: cancels other order option allows you to place a pair of orders stipulating
-          that if one order is executed fully or partially, then the other is automatically canceled
-        @param aff_code: bitfinex affiliate code
-        @param time_in_force:	datetime for automatic order cancellation ie. 2020-01-01 10:45:23
-        @param leverage: the amount of leverage to apply to the order as an integer
-        @param onConfirm: function called when the bitfinex websocket receives signal that the order
-          was confirmed
-        @param onClose: function called when the bitfinex websocket receives signal that the order
-          was closed due to being filled or cancelled
-        """
-        cid = self._gen_unique_cid()
-        # create base payload with required data
-        payload = {
-            "cid": cid,
-            "type": str(market_type),
-            "symbol": symbol,
-            "amount": str(amount),
-            "price": str(price),
-            "meta": {}
-        }
-        # calculate and add flags
-        flags = calculate_order_flags(hidden, close, reduce_only, post_only, oco)
-        payload['flags'] = flags
-        # add extra parameters
-        if price_trailing != None:
-            payload['price_trailing'] = price_trailing
-        if price_aux_limit != None:
-            payload['price_aux_limit'] = price_aux_limit
-        if oco_stop_price != None:
-            payload['price_oco_stop'] = str(oco_stop_price)
-        if time_in_force != None:
-            payload['tif'] = time_in_force
-        if gid != None:
-            payload['gid'] = gid
-        if leverage != None:
-            payload['lev'] = str(leverage)
-        if aff_code != None:
-            payload['meta']['aff_code'] = str(aff_code)
-        # submit the order
-        self.pending_orders[cid] = payload
-        self._create_callback(cid, onConfirm, self.pending_order_confirm_callbacks)
-        self._create_callback(cid, onClose, self.pending_order_close_callbacks)
-        await self.bfxapi._send_auth_command('on', payload)
-        self.logger.info("Order cid={} ({} {} @ {}) dispatched".format(
-            cid, symbol, amount, price))
-
-    async def update_order(self, orderId, price=None, amount=None, delta=None, price_aux_limit=None,
-                           price_trailing=None, hidden=False, close=False, reduce_only=False,
-                           post_only=False, time_in_force=None, leverage=None, onConfirm=None):
-        """
-        Update an existing order
-
-        @param orderId: the id of the order that you want to update
-        @param price: the price you want to buy/sell at (must be positive)
-        @param amount: order size: how much you want to buy/sell,
-          a negative amount indicates a sell order and positive a buy order
-        @param delta:	change of amount
-        @param price_trailing:	decimal trailing price
-        @param price_aux_limit:	decimal	auxiliary Limit price (only for STOP LIMIT)
-        @param hidden: if True, order should be hidden from orderbooks
-        @param close: if True, close position if position present
-        @param reduce_only: if True, ensures that the executed order does not flip the opened position
-        @param post_only: if True, ensures the limit order will be added to the order book and not
-          match with a pre-existing order
-        @param time_in_force: datetime for automatic order cancellation ie. 2020-01-01 10:45:23
-        @param leverage: the amount of leverage to apply to the order as an integer
-        @param onConfirm: function called when the bitfinex websocket receives signal that the order
-          was confirmed
-        @param onClose: function called when the bitfinex websocket receives signal that the order
-          was closed due to being filled or cancelled
-        """
-        self._create_callback(orderId, onConfirm, self.pending_update_confirm_callbacks)
-        payload = {"id": orderId}
-        if price != None:
-            payload['price'] = str(price)
-        if amount != None:
-            payload['amount'] = str(amount)
-        if delta != None:
-            payload['delta'] = str(delta)
-        if price_aux_limit != None:
-            payload['price_aux_limit'] = str(price_aux_limit)
-        if price_trailing != None:
-            payload['price_trailing'] = str(price_trailing)
-        if time_in_force != None:
-            payload['tif'] = str(time_in_force)
-        if leverage != None:
-            payload['lev'] = str(leverage)
-        flags = calculate_order_flags(
-            hidden, close, reduce_only, post_only, False)
-        payload['flags'] = flags
-        await self.bfxapi._send_auth_command('ou', payload)
-        self.logger.info("Update Order order_id={} dispatched".format(orderId))
-
-    async def cancel_order(self, orderId, onConfirm=None):
-        """
-        Cancel an existing open order
-
-        @param orderId: the id of the order that you want to update
-        @param onConfirm: function called when the bitfinex websocket receives signal that the
-                          order was confirmed
-        """
-        self._create_callback(orderId, onConfirm, self.pending_cancel_confirm_callbacks)
-        await self.bfxapi._send_auth_command('oc', {'id': orderId})
-        self.logger.info("Order cancel order_id={} dispatched".format(orderId))
-
-    async def cancel_all_orders(self):
-        """
-        Cancel all existing open orders
-
-        This function closes all open orders.
-        """
-        await self.bfxapi._send_auth_command('oc_multi', { 'all': 1 })
-
-    async def cancel_order_group(self, gid, onConfirm=None):
-        """
-        Cancel a set of orders using a single group id.
-        """
-        self._create_callback(gid, onConfirm, self.pending_cancel_confirm_callbacks)
-        await self.bfxapi._send_auth_command('oc_multi', { 'gid': [gid] })
-
-    async def cancel_order_multi(self, ids=None, gids=None):
-        """
-        Cancel existing open orders as a batch
-
-        @param ids: an array of order ids
-        @param gids: an array of group ids
-        """
-        payload = {}
-        if ids:
-            payload['id'] = ids
-        if gids:
-            payload['gid'] = gids
-        await self.bfxapi._send_auth_command('oc_multi', payload)
-
-    def _create_callback(self, identifier, func, callback_storage):
-        if not func:
-            return
-        if identifier in callback_storage:
-            callback_storage[identifier] += [func]
-        else:
-            callback_storage[identifier] = [func]
-
-    async def _execute_callback(self, order, callback_storage):
-        idents = [order.id, order.cid, order.gid]
-        tasks = []
-        key = None
-        for k in callback_storage.keys():
-            if k in idents:
-                key = k
-                # call all callbacks associated with identifier
-                for callback in callback_storage[k]:
-                    tasks += [callback(order)]
-                break
-        # remove from callbacks
-        if key:
-            del callback_storage[key]
-        await asyncio.gather(*tasks)
-
-    def _gen_unique_cid(self):
-        return gen_unique_cid()
diff --git a/bfxapi/websockets/subscription_manager.py b/bfxapi/websockets/subscription_manager.py
deleted file mode 100644
index b5168671..00000000
--- a/bfxapi/websockets/subscription_manager.py
+++ /dev/null
@@ -1,207 +0,0 @@
-"""
-Module used to house all of the functions/classes used to handle
-subscriptions
-"""
-
-import json
-import asyncio
-import time
-
-from ..utils.custom_logger import CustomLogger
-from ..models import Subscription
-
-MAX_CHANNEL_COUNT = 25
-
-class SubscriptionManager:
-
-    def __init__(self, bfxapi, logLevel='INFO'):
-        self.pending_subscriptions = {}
-        self.subscriptions_chanid = {}
-        self.subscriptions_subid = {}
-        self.unsubscribe_callbacks = {}
-        self.bfxapi = bfxapi
-        self.logger = CustomLogger('BfxSubscriptionManager', logLevel=logLevel)
-
-    def get_sub_count_by_socket(self, socket_id):
-        count = 0
-        for sub in self.subscriptions_chanid.values():
-            if sub.socket.id == socket_id and sub.is_subscribed():
-                count += 1
-        for sub in self.pending_subscriptions.values():
-            if sub.socket.id == socket_id:
-                count += 1
-        return count
-
-    async def subscribe(self, channel_name, symbol, key=None, timeframe=None, **kwargs):
-        """
-        Subscribe to a new channel
-
-        @param channel_name: the name of the channel i.e 'books', 'candles'
-        @param symbol: the trading symbol i.e 'tBTCUSD'
-        @param timeframe: sepecifies the data timeframe between each candle (only required
-          for the candles channel)
-        """
-        if self.bfxapi.get_total_available_capcity() < 2:
-            sId = self.bfxapi._start_new_socket()
-            self.bfxapi._wait_for_socket(sId)
-            soc = self.bfxapi.sockets[sId]
-            socket = self.bfxapi.sockets[sId]
-        else:
-            # get the socket with the least amount of subscriptions
-            socket = self.bfxapi.get_most_available_socket()
-        # create a new subscription
-        subscription = Subscription(
-            socket, channel_name, symbol, key, timeframe, **kwargs)
-        self.logger.info("Subscribing to channel {}".format(channel_name))
-        self.pending_subscriptions[subscription.get_key()] = subscription
-
-        await subscription.subscribe()
-
-    async def confirm_subscription(self, socket_id, raw_ws_data):
-        symbol = raw_ws_data.get("symbol", None)
-        channel = raw_ws_data.get("channel")
-        chan_id = raw_ws_data.get("chanId")
-        key = raw_ws_data.get("key", None)
-        p_sub = None
-        get_key = "{}_{}".format(channel, key or symbol)
-        if chan_id in self.subscriptions_chanid:
-            # subscription has already existed in the past
-            p_sub = self.subscriptions_chanid[chan_id]
-        elif get_key in self.pending_subscriptions:
-            # has just been created and is pending
-            p_sub = self.pending_subscriptions[get_key]
-            # remove from pending list
-            del self.pending_subscriptions[get_key]
-        else:
-            # might have been disconnected, so we need to check if exists
-            # as subscribed but with a new channel ID
-            for sub in self.subscriptions_chanid.values():
-                if sub.get_key() == get_key and not sub.is_subscribed():
-                    # delete old channelId
-                    del self.subscriptions_chanid[sub.chan_id]
-                    p_sub = sub
-                    break
-        if p_sub is None:
-            # no sub matches confirmation
-            self.logger.warn("unknown subscription confirmed {}".format(get_key))
-            return
-
-        p_sub.confirm_subscription(chan_id)
-        # add to confirmed list
-        self.subscriptions_chanid[chan_id] = p_sub
-        self.subscriptions_subid[p_sub.sub_id] = p_sub
-        self.bfxapi._emit('subscribed', p_sub)
-
-    async def confirm_unsubscribe(self, socket_id, raw_ws_data):
-        chan_id = raw_ws_data.get("chanId")
-        sub = self.subscriptions_chanid[chan_id]
-        sub.confirm_unsubscribe()
-        # call onComplete callback if exists
-        if sub.sub_id in self.unsubscribe_callbacks:
-            await self.unsubscribe_callbacks[sub.sub_id]()
-            del self.unsubscribe_callbacks[sub.sub_id]
-        self.bfxapi._emit('unsubscribed', sub)
-
-    def get(self, chan_id):
-        return self.subscriptions_chanid[chan_id]
-
-    def set_unsubscribed_by_socket(self, socket_id):
-        """
-        Sets all f the subscriptions ot state 'unsubscribed'
-        """
-        for sub in self.subscriptions_chanid.values():
-            if sub.socket.id == socket_id:
-                sub.confirm_unsubscribe()
-
-    def set_all_unsubscribed(self):
-        """
-        Sets all f the subscriptions ot state 'unsubscribed'
-        """
-        for sub in self.subscriptions_chanid.values():
-            sub.confirm_unsubscribe()
-
-    async def unsubscribe(self, chan_id, onComplete=None):
-        """
-        Unsubscribe from the channel with the given chanId
-
-        @param onComplete: function called when the bitfinex websocket resoponds with
-          a signal that confirms the subscription has been unsubscribed to
-        """
-        sub = self.subscriptions_chanid[chan_id]
-        if onComplete:
-            self.unsubscribe_callbacks[sub.sub_id] = onComplete
-        if sub.is_subscribed():
-            await self.subscriptions_chanid[chan_id].unsubscribe()
-
-    async def resubscribe(self, chan_id):
-        """
-        Unsubscribes and then subscribes to the channel with the given Id
-
-        This function is mostly used to force the channel to produce a fresh snapshot.
-        """
-        sub = self.subscriptions_chanid[chan_id]
-
-        async def re_sub():
-            await sub.subscribe()
-        if sub.is_subscribed():
-            # unsubscribe first and call callback to subscribe
-            await self.unsubscribe(chan_id, re_sub)
-        else:
-            # already unsubscribed, so just subscribe
-            await sub.subscribe()
-
-    def channel_count(self):
-        """
-        Returns the number of cannels
-        """
-        return len(self.pending_subscriptions) + len(self.subscriptions_chanid)
-
-    def is_subscribed(self, chan_id):
-        """
-        Returns True if the channel with the given chanId is currenly subscribed to
-        """
-        if chan_id not in self.subscriptions_chanid:
-            return False
-        return self.subscriptions_chanid[chan_id].is_subscribed()
-
-    async def unsubscribe_all(self):
-        """
-        Unsubscribe from all channels.
-        """
-        task_batch = []
-        for chan_id in self.subscriptions_chanid:
-            sub = self.get(chan_id)
-            if sub.is_subscribed():
-                task_batch += [
-                    asyncio.ensure_future(self.unsubscribe(chan_id))
-                ]
-        if len(task_batch) == 0:
-            return
-        await asyncio.wait(*[task_batch])
-
-    async def resubscribe_by_socket(self, socket_id):
-        """
-        Unsubscribe channels on socket and then subscribe to all channels
-        """
-        task_batch = []
-        for sub in self.subscriptions_chanid.values():
-            if sub.socket.id == socket_id:
-                task_batch += [
-                    asyncio.ensure_future(self.resubscribe(sub.chan_id))
-                ]
-        if len(task_batch) == 0:
-            return
-        await asyncio.wait(*[task_batch])
-
-    async def resubscribe_all(self):
-        """
-        Unsubscribe and then subscribe to all channels
-        """
-        task_batch = []
-        for chan_id in self.subscriptions_chanid:
-            task_batch += [
-                asyncio.ensure_future(self.resubscribe(chan_id))
-            ]
-        if len(task_batch) == 0:
-            return
-        await asyncio.wait(*[task_batch])
diff --git a/bfxapi/websockets/wallet_manager.py b/bfxapi/websockets/wallet_manager.py
deleted file mode 100644
index a91c66ec..00000000
--- a/bfxapi/websockets/wallet_manager.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""
-Module used to handle wallet updates and data types
-"""
-
-from ..models import Wallet
-
-
-class WalletManager:
-    """
-    This class is used to interact with all of the different wallets
-    """
-
-    def __init__(self):
-        self.wallets = {}
-
-    def _update_from_snapshot(self, raw_ws_data):
-        wData = raw_ws_data[2]
-        self.wallets = {}
-        for wallet in wData:
-            new_wallet = Wallet(wallet[0], wallet[1], wallet[2], wallet[3], wallet[4])
-            self.wallets[new_wallet.key] = new_wallet
-        return self.get_wallets()
-
-    def _update_from_event(self, raw_ws_data):
-        wallet = raw_ws_data[2]
-        new_wallet = Wallet(wallet[0], wallet[1], wallet[2], wallet[3], wallet[4])
-        self.wallets[new_wallet.key] = new_wallet
-        return new_wallet
-
-    def get_wallets(self):
-        return list(self.wallets.values())
diff --git a/dev-requirements.txt b/dev-requirements.txt
new file mode 100644
index 00000000..fff03cf0
Binary files /dev/null and b/dev-requirements.txt differ
diff --git a/docs/regenerate_docs.md b/docs/regenerate_docs.md
deleted file mode 100644
index 9bdf031d..00000000
--- a/docs/regenerate_docs.md
+++ /dev/null
@@ -1,19 +0,0 @@
-For development purposes, these are the commands used to generate the doc files.
-
-Install pydocmd:
-
-```
-pip3 install pydoc-markdown
-```
-
-Generate REST V2 docs:
-
-```
-pydocmd simple bfxapi bfxapi.client+ bfxapi.rest.bfx_rest.BfxRest+ > ./docs/rest_v2.md
-```
-
-Generate Websocket V2 docs:
-
-```
-pydocmd simple bfxapi bfxapi.client+ bfxapi.websockets.bfx_websocket.BfxWebsocket+ > ./docs/ws_v2.md
-```
diff --git a/docs/rest_v2.md b/docs/rest_v2.md
deleted file mode 100644
index e4caafcc..00000000
--- a/docs/rest_v2.md
+++ /dev/null
@@ -1,543 +0,0 @@
-
-# bfxapi
-
-This module is used to interact with the bitfinex api
-
-
-# bfxapi.client
-
-This module exposes the core bitfinex clients which includes both
-a websocket client and a rest interface client
-
-
-## Client
-```python
-Client(self,
-       API_KEY=None,
-       API_SECRET=None,
-       rest_host='https://api-pub.bitfinex.com/v2',
-       ws_host='wss://api-pub.bitfinex.com/ws/2',
-       create_event_emitter=None,
-       logLevel='INFO',
-       dead_man_switch=False,
-       ws_capacity=25,
-       channel_filter=[],
-       *args,
-       **kwargs)
-```
-
-The bfx client exposes rest and websocket objects
-
-
-# BfxRest
-```python
-BfxRest(self,
-        API_KEY,
-        API_SECRET,
-        host='https://api-pub.bitfinex.com/v2',
-        loop=None,
-        logLevel='INFO',
-        parse_float=float,
-        *args,
-        **kwargs)
-```
-
-BFX rest interface contains functions which are used to interact with both the public
-and private Bitfinex http api's.
-To use the private api you have to set the API_KEY and API_SECRET variables to your
-api key.
-
-
-## fetch
-```python
-BfxRest.fetch(endpoint, params='')
-```
-
-Send a GET request to the bitfinex api
-
-@return reponse
-
-
-## post
-```python
-BfxRest.post(endpoint, data={}, params='')
-```
-
-Send a pre-signed POST request to the bitfinex api
-
-@return response
-
-
-## get_seed_candles
-```python
-BfxRest.get_seed_candles(symbol, tf='1m')
-```
-
-Used by the honey framework, this function gets the last 4k candles.
-
-
-## get_public_candles
-```python
-BfxRest.get_public_candles(symbol,
-                           start,
-                           end,
-                           section='hist',
-                           tf='1m',
-                           limit='100',
-                           sort=-1)
-```
-
-Get all of the public candles between the start and end period.
-
-__Attributes__
-
-- `@param symbol symbol string`: pair symbol i.e tBTCUSD
-- `@param secton string`: available values: "last", "hist"
-- `@param start int`: millisecond start time
-- `@param end int`: millisecond end time
-- `@param limit int`: max number of items in response
-- `@param tf int`: timeframe inbetween candles i.e 1m (min), ..., 1D (day),
-              ... 1M (month)
-- `@param sort int`: if = 1 it sorts results returned with old > new
-@return Array [ MTS, OPEN, CLOSE, HIGH, LOW, VOLUME ]
-
-
-## get_public_trades
-```python
-BfxRest.get_public_trades(symbol, start, end, limit='100', sort=-1)
-```
-
-Get all of the public trades between the start and end period.
-
-__Attributes__
-
-- `@param symbol symbol string`: pair symbol i.e tBTCUSD
-- `@param start int`: millisecond start time
-- `@param end int`: millisecond end time
-- `@param limit int`: max number of items in response
-@return Array [ ID, MTS, AMOUNT, RATE, PERIOD? ]
-
-
-## get_public_books
-```python
-BfxRest.get_public_books(symbol, precision='P0', length=25)
-```
-
-Get the public orderbook for a given symbol.
-
-__Attributes__
-
-- `@param symbol symbol string`: pair symbol i.e tBTCUSD
-- `@param precision string`: level of price aggregation (P0, P1, P2, P3, P4, R0)
-- `@param length int`: number of price points ("25", "100")
-@return Array [ PRICE, COUNT, AMOUNT ]
-
-
-## get_public_ticker
-```python
-BfxRest.get_public_ticker(symbol)
-```
-
-Get tickers for the given symbol. Tickers shows you the current best bid and ask,
-as well as the last trade price.
-
-__Attributes__
-
-- `@param symbols symbol string`: pair symbol i.e tBTCUSD
-@return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE,
-  DAILY_CHANGE_PERC, LAST_PRICE, VOLUME, HIGH, LOW ]
-
-
-## get_public_tickers
-```python
-BfxRest.get_public_tickers(symbols)
-```
-
-Get tickers for the given symbols. Tickers shows you the current best bid and ask,
-as well as the last trade price.
-
-__Attributes__
-
-- `@param symbols Array<string>`: array of symbols i.e [tBTCUSD, tETHUSD]
-@return Array [ SYMBOL, BID, BID_SIZE, ASK, ASK_SIZE, DAILY_CHANGE,  DAILY_CHANGE_PERC,
-  LAST_PRICE, VOLUME, HIGH, LOW ]
-
-
-## get_derivative_status
-```python
-BfxRest.get_derivative_status(symbol)
-```
-
-Gets platform information for derivative symbol.
-
-__Attributes__
-
-- `@param derivativeSymbol string`: i.e tBTCF0:USTF0
-@return [KEY/SYMBOL, MTS, PLACEHOLDER, DERIV_PRICE, SPOT_PRICE, PLACEHOLDER, INSURANCE_FUND_BALANCE4,
-    PLACEHOLDER, PLACEHOLDER, FUNDING_ACCRUED, FUNDING_STEP, PLACEHOLDER]
-
-
-## get_derivative_statuses
-```python
-BfxRest.get_derivative_statuses(symbols)
-```
-
-Gets platform information for a collection of derivative symbols.
-
-__Attributes__
-
-- `@param derivativeSymbols Array<string>`: array of symbols i.e [tBTCF0:USTF0 ...] or ["ALL"]
-@return [KEY/SYMBOL, MTS, PLACEHOLDER, DERIV_PRICE, SPOT_PRICE, PLACEHOLDER, INSURANCE_FUND_BALANCE4,
-    PLACEHOLDER, PLACEHOLDER, FUNDING_ACCRUED, FUNDING_STEP, PLACEHOLDER]
-
-
-## get_wallets
-```python
-BfxRest.get_wallets()
-```
-
-Get all wallets on account associated with API_KEY - Requires authentication.
-
-@return Array <models.Wallet>
-
-
-## get_active_orders
-```python
-BfxRest.get_active_orders(symbol)
-```
-
-Get all of the active orders associated with API_KEY - Requires authentication.
-
-__Attributes__
-
-- `@param symbol string`: pair symbol i.e tBTCUSD
-@return Array <models.Order>
-
-
-## get_order_history
-```python
-BfxRest.get_order_history(symbol, start, end, limit=25, sort=-1)
-```
-
-Get all of the orders between the start and end period associated with API_KEY
-- Requires authentication.
-
-__Attributes__
-
-- `@param symbol string`: pair symbol i.e tBTCUSD
-- `@param start int`: millisecond start time
-- `@param end int`: millisecond end time
-- `@param limit int`: max number of items in response
-@return Array <models.Order>
-
-
-## get_active_position
-```python
-BfxRest.get_active_position()
-```
-
-Get all of the active position associated with API_KEY - Requires authentication.
-
-@return Array <models.Position>
-
-
-## get_order_trades
-```python
-BfxRest.get_order_trades(symbol, order_id)
-```
-
-Get all of the trades that have been generated by the given order associated with API_KEY
-- Requires authentication.
-
-__Attributes__
-
-- `@param symbol string`: pair symbol i.e tBTCUSD
-- `@param order_id string`: id of the order
-@return Array <models.Trade>
-
-
-## get_trades
-```python
-BfxRest.get_trades(symbol, start, end, limit=25)
-```
-
-Get all of the trades between the start and end period associated with API_KEY
-- Requires authentication.
-
-__Attributes__
-
-- `@param symbol string`: pair symbol i.e tBTCUSD
-- `@param start int`: millisecond start time
-- `@param end int`: millisecond end time
-- `@param limit int`: max number of items in response
-@return Array <models.Trade>
-
-
-## get_funding_offers
-```python
-BfxRest.get_funding_offers(symbol)
-```
-
-Get all of the funding offers associated with API_KEY - Requires authentication.
-
-@return Array <models.FundingOffer>
-
-
-## get_funding_offer_history
-```python
-BfxRest.get_funding_offer_history(symbol, start, end, limit=25)
-```
-
-Get all of the funding offers between the start and end period associated with API_KEY
-- Requires authentication.
-
-__Attributes__
-
-- `@param symbol string`: pair symbol i.e tBTCUSD
-- `@param start int`: millisecond start time
-- `@param end int`: millisecond end time
-- `@param limit int`: max number of items in response
-@return Array <models.FundingOffer>
-
-
-## get_funding_loans
-```python
-BfxRest.get_funding_loans(symbol)
-```
-
-Get all of the funding loans associated with API_KEY - Requires authentication.
-
-@return Array <models.FundingLoan>
-
-
-## get_funding_loan_history
-```python
-BfxRest.get_funding_loan_history(symbol, start, end, limit=25)
-```
-
-Get all of the funding loans between the start and end period associated with API_KEY
-- Requires authentication.
-
-__Attributes__
-
-- `@param symbol string`: pair symbol i.e tBTCUSD
-- `@param start int`: millisecond start time
-- `@param end int`: millisecond end time
-- `@param limit int`: max number of items in response
-@return Array <models.FundingLoan>
-
-
-## get_funding_credit_history
-```python
-BfxRest.get_funding_credit_history(symbol, start, end, limit=25)
-```
-
-Get all of the funding credits between the start and end period associated with API_KEY
-- Requires authentication.
-
-__Attributes__
-
-- `@param symbol string`: pair symbol i.e tBTCUSD
-- `@param start int`: millisecond start time
-- `@param end int`: millisecond end time
-- `@param limit int`: max number of items in response
-@return Array <models.FundingCredit>
-
-
-## submit_funding_offer
-```python
-BfxRest.submit_funding_offer(symbol,
-                             amount,
-                             rate,
-                             period,
-                             funding_type='LIMIT',
-                             hidden=False)
-```
-
-Submits a new funding offer
-
-__Attributes__
-
-- `@param symbol string`: pair symbol i.e fUSD
-- `@param amount float`: funding size
-- `@param rate float`: percentage rate to charge per a day
-- `@param period int`: number of days for funding to remain active once accepted
-
-
-## submit_cancel_funding_offer
-```python
-BfxRest.submit_cancel_funding_offer(fundingId)
-```
-
-Cancel a funding offer
-
-__Attributes__
-
-- `@param fundingId int`: the id of the funding offer
-
-
-## submit_wallet_transfer
-```python
-BfxRest.submit_wallet_transfer(from_wallet, to_wallet, from_currency,
-                               to_currency, amount)
-```
-
-Transfer funds between wallets
-
-__Attributes__
-
-- `@param from_wallet string`: wallet name to transfer from i.e margin, exchange
-- `@param to_wallet string`: wallet name to transfer to i.e margin, exchange
-- `@param from_currency string`: currency symbol to tranfer from i.e BTC, USD
-- `@param to_currency string`: currency symbol to transfer to i.e BTC, USD
-- `@param amount float`: amount of funds to transfer
-
-
-## get_wallet_deposit_address
-```python
-BfxRest.get_wallet_deposit_address(wallet, method, renew=0)
-```
-
-Get the deposit address for the given wallet and protocol
-
-__Attributes__
-
-- `@param wallet string`: wallet name i.e margin, exchange
-- `@param method string`: transfer protocol i.e bitcoin
-
-
-## create_wallet_deposit_address
-```python
-BfxRest.create_wallet_deposit_address(wallet, method)
-```
-
-Creates a new deposit address for the given wallet and protocol.
-Previously generated addresses remain linked.
-
-__Attributes__
-
-- `@param wallet string`: wallet name i.e margin, exchange
-- `@param method string`: transfer protocol i.e bitcoin
-
-
-## submit_wallet_withdraw
-```python
-BfxRest.submit_wallet_withdraw(wallet, method, amount, address)
-```
-
-Submits a request to withdraw crypto funds to an external wallet.
-
-__Attributes__
-
-- `@param wallet string`: wallet name i.e margin, exchange
-- `@param method string`: transfer protocol i.e bitcoin
-- `@param amount float`: amount of funds to withdraw
-- `@param address string`: external address to withdraw to
-
-
-## submit_order
-```python
-BfxRest.submit_order(symbol,
-                     price,
-                     amount,
-                     market_type='LIMIT',
-                     hidden=False,
-                     price_trailing=None,
-                     price_aux_limit=None,
-                     oco_stop_price=None,
-                     close=False,
-                     reduce_only=False,
-                     post_only=False,
-                     oco=False,
-                     aff_code=None,
-                     time_in_force=None,
-                     leverage=None,
-                     gid=None)
-```
-
-Submit a new order
-
-__Attributes__
-
-- `@param gid`: assign the order to a group identifier
-- `@param symbol`: the name of the symbol i.e 'tBTCUSD
-- `@param price`: the price you want to buy/sell at (must be positive)
-- `@param amount`: order size: how much you want to buy/sell,
-  a negative amount indicates a sell order and positive a buy order
-- `@param market_type	Order.Type`: please see Order.Type enum
-  amount	decimal string	Positive for buy, Negative for sell
-- `@param hidden`: if True, order should be hidden from orderbooks
-- `@param price_trailing`:	decimal trailing price
-- `@param price_aux_limit`:	decimal	auxiliary Limit price (only for STOP LIMIT)
-- `@param oco_stop_price`: set the oco stop price (requires oco = True)
-- `@param close`: if True, close position if position present
-- `@param reduce_only`: if True, ensures that the executed order does not flip the opened position
-- `@param post_only`: if True, ensures the limit order will be added to the order book and not
-  match with a pre-existing order
-- `@param oco`: cancels other order option allows you to place a pair of orders stipulating
-  that if one order is executed fully or partially, then the other is automatically canceled
-- `@param aff_code`: bitfinex affiliate code
-- `@param time_in_force`:	datetime for automatic order cancellation ie. 2020-01-01 10:45:23
-- `@param leverage`: the amount of leverage to apply to the order as an integer
-
-
-## submit_cancel_order
-```python
-BfxRest.submit_cancel_order(orderId)
-```
-
-Cancel an existing open order
-
-__Attributes__
-
-- `@param orderId`: the id of the order that you want to update
-
-
-## submit_update_order
-```python
-BfxRest.submit_update_order(orderId,
-                            price=None,
-                            amount=None,
-                            delta=None,
-                            price_aux_limit=None,
-                            price_trailing=None,
-                            hidden=False,
-                            close=False,
-                            reduce_only=False,
-                            post_only=False,
-                            time_in_force=None,
-                            leverage=None)
-```
-
-Update an existing order
-
-__Attributes__
-
-- `@param orderId`: the id of the order that you want to update
-- `@param price`: the price you want to buy/sell at (must be positive)
-- `@param amount`: order size: how much you want to buy/sell,
-  a negative amount indicates a sell order and positive a buy order
-- `@param delta`:	change of amount
-- `@param price_trailing`:	decimal trailing price
-- `@param price_aux_limit`:	decimal	auxiliary Limit price (only for STOP LIMIT)
-- `@param hidden`: if True, order should be hidden from orderbooks
-- `@param close`: if True, close position if position present
-- `@param reduce_only`: if True, ensures that the executed order does not flip the opened position
-- `@param post_only`: if True, ensures the limit order will be added to the order book and not
-  match with a pre-existing order
-- `@param time_in_force`:	datetime for automatic order cancellation ie. 2020-01-01 10:45:23
-- `@param leverage`: the amount of leverage to apply to the order as an integer
-
-
-## set_derivative_collateral
-```python
-BfxRest.set_derivative_collateral(symbol, collateral)
-```
-
-Update the amount of callateral used to back a derivative position.
-
-__Attributes__
-
-- `@param symbol of the derivative i.e 'tBTCF0`:USTF0'
-- `@param collateral`: amount of collateral/value to apply to the open position
-
diff --git a/docs/ws_v2.md b/docs/ws_v2.md
deleted file mode 100644
index f0cc3bbd..00000000
--- a/docs/ws_v2.md
+++ /dev/null
@@ -1,331 +0,0 @@
-
-# bfxapi
-
-This module is used to interact with the bitfinex api
-
-
-# bfxapi.client
-
-This module exposes the core bitfinex clients which includes both
-a websocket client and a rest interface client
-
-
-## Client
-```python
-Client(self,
-       API_KEY=None,
-       API_SECRET=None,
-       rest_host='https://api-pub.bitfinex.com/v2',
-       ws_host='wss://api-pub.bitfinex.com/ws/2',
-       create_event_emitter=None,
-       logLevel='INFO',
-       dead_man_switch=False,
-       ws_capacity=25,
-       channel_filter=[],
-       *args,
-       **kwargs)
-```
-
-The bfx client exposes rest and websocket objects
-
-
-# BfxWebsocket
-```python
-BfxWebsocket(self,
-             API_KEY=None,
-             API_SECRET=None,
-             host='wss://api-pub.bitfinex.com/ws/2',
-             manageOrderBooks=False,
-             dead_man_switch=False,
-             ws_capacity=25,
-             logLevel='INFO',
-             parse_float=float,
-             channel_filter=[],
-             *args,
-             **kwargs)
-```
-
-More complex websocket that heavily relies on the btfxwss module.
-This websocket requires authentication and is capable of handling orders.
-https://github.com/Crypto-toolbox/btfxwss
-
-### Emitter events:
-  - `all` (array|Object): listen for all messages coming through
-  - `connected:` () called when a connection is made
-  - `disconnected`: () called when a connection is ended (A reconnect attempt may follow)
-  - `stopped`: () called when max amount of connection retries is met and the socket is closed
-  - `authenticated` (): called when the websocket passes authentication
-  - `notification` (Notification): incoming account notification
-  - `error` (array): error from the websocket
-  - `order_closed` (Order, Trade): when an order has been closed
-  - `order_update` (Order, Trade): when an order has been updated
-  - `order_new` (Order, Trade): when an order has been created but not closed. Note: will not be called if order is executed and filled instantly
-  - `order_confirmed` (Order, Trade): When an order has been submitted and received
-  - `wallet_snapshot` (array[Wallet]): Initial wallet balances (Fired once)
-  - `order_snapshot` (array[Order]): Initial open orders (Fired once)
-  - `positions_snapshot` (array): Initial open positions (Fired once)
-  - `positions_new` (array): Initial open positions (Fired once)
-  - `positions_update` (array): An active position has been updated
-  - `positions_close` (array): An active position has closed
-  - `wallet_update` (Wallet): Changes to the balance of wallets
-  - `status_update` (Object): New platform status info
-  - `seed_candle` (Object): Initial past candle to prime strategy
-  - `seed_trade` (Object): Initial past trade to prime strategy
-  - `funding_offer_snapshot` (array): Opening funding offer balances
-  - `funding_loan_snapshot` (array): Opening funding loan balances
-  - `funding_credit_snapshot` (array): Opening funding credit balances
-  - `balance_update` (array): When the state of a balance is changed
-  - `new_trade` (array): A new trade on the market has been executed
-  - `new_user_trade` (array): A new - your - trade has been executed
-  - `new_ticker` (Ticker|FundingTicker): A new ticker update has been published
-  - `new_funding_ticker` (FundingTicker): A new funding ticker update has been published
-  - `new_trading_ticker` (Ticker): A new trading ticker update has been published
-  - `trade_update` (array): A trade on the market has been updated
-  - `user_trade_update` (array): A - your - trade has been updated
-  - `new_candle` (array): A new candle has been produced
-  - `margin_info_updates` (array): New margin information has been broadcasted
-  - `funding_info_updates` (array): New funding information has been broadcasted
-  - `order_book_snapshot` (array): Initial snapshot of the order book on connection
-  - `order_book_update` (array): A new order has been placed into the ordebrook
-  - `subscribed` (Subscription): A new channel has been subscribed to
-  - `unsubscribed` (Subscription): A channel has been un-subscribed
-
-
-## enable_flag
-```python
-BfxWebsocket.enable_flag(flag)
-```
-
-Enable flag on websocket connection
-
-__Attributes__
-
-- `flag (int)`: int flag value
-
-
-## subscribe_order_book
-```python
-BfxWebsocket.subscribe_order_book(symbol)
-```
-
-Subscribe to an orderbook data feed
-
-__Attributes__
-
-- `@param symbol`: the trading symbol i.e 'tBTCUSD'
-
-
-## subscribe_candles
-```python
-BfxWebsocket.subscribe_candles(symbol, timeframe)
-```
-
-Subscribe to a candle data feed
-
-__Attributes__
-
-- `@param symbol`: the trading symbol i.e 'tBTCUSD'
-- `@param timeframe`: resolution of the candle i.e 15m, 1h
-
-
-## subscribe_trades
-```python
-BfxWebsocket.subscribe_trades(symbol)
-```
-
-Subscribe to a trades data feed
-
-__Attributes__
-
-- `@param symbol`: the trading symbol i.e 'tBTCUSD'
-
-
-## subscribe_ticker
-```python
-BfxWebsocket.subscribe_ticker(symbol)
-```
-
-Subscribe to a ticker data feed
-
-__Attributes__
-
-- `@param symbol`: the trading symbol i.e 'tBTCUSD'
-
-
-## subscribe_derivative_status
-```python
-BfxWebsocket.subscribe_derivative_status(symbol)
-```
-
-Subscribe to a status data feed
-
-__Attributes__
-
-- `@param symbol`: the trading symbol i.e 'tBTCUSD'
-
-
-## subscribe
-```python
-BfxWebsocket.subscribe(*args, **kwargs)
-```
-
-Subscribe to a new channel
-
-__Attributes__
-
-- `@param channel_name`: the name of the channel i.e 'books', 'candles'
-- `@param symbol`: the trading symbol i.e 'tBTCUSD'
-- `@param timeframe`: sepecifies the data timeframe between each candle (only required
-  for the candles channel)
-
-
-## unsubscribe
-```python
-BfxWebsocket.unsubscribe(*args, **kwargs)
-```
-
-Unsubscribe from the channel with the given chanId
-
-__Attributes__
-
-- `@param onComplete`: function called when the bitfinex websocket resoponds with
-  a signal that confirms the subscription has been unsubscribed to
-
-
-## resubscribe
-```python
-BfxWebsocket.resubscribe(*args, **kwargs)
-```
-
-Unsubscribes and then subscribes to the channel with the given Id
-
-This function is mostly used to force the channel to produce a fresh snapshot.
-
-
-## unsubscribe_all
-```python
-BfxWebsocket.unsubscribe_all(*args, **kwargs)
-```
-
-Unsubscribe from all channels.
-
-
-## resubscribe_all
-```python
-BfxWebsocket.resubscribe_all(*args, **kwargs)
-```
-
-Unsubscribe and then subscribe to all channels
-
-
-## submit_order
-```python
-BfxWebsocket.submit_order(*args, **kwargs)
-```
-
-Submit a new order
-
-__Attributes__
-
-- `@param gid`: assign the order to a group identifier
-- `@param symbol`: the name of the symbol i.e 'tBTCUSD
-- `@param price`: the price you want to buy/sell at (must be positive)
-- `@param amount`: order size: how much you want to buy/sell,
-  a negative amount indicates a sell order and positive a buy order
-- `@param market_type	Order.Type`: please see Order.Type enum
-  amount	decimal string	Positive for buy, Negative for sell
-- `@param hidden`: if True, order should be hidden from orderbooks
-- `@param price_trailing`:	decimal trailing price
-- `@param price_aux_limit`:	decimal	auxiliary Limit price (only for STOP LIMIT)
-- `@param oco_stop_price`: set the oco stop price (requires oco = True)
-- `@param close`: if True, close position if position present
-- `@param reduce_only`: if True, ensures that the executed order does not flip the opened position
-- `@param post_only`: if True, ensures the limit order will be added to the order book and not
-  match with a pre-existing order
-- `@param oco`: cancels other order option allows you to place a pair of orders stipulating
-  that if one order is executed fully or partially, then the other is automatically canceled
-
-@param time_in_force:	datetime for automatic order cancellation ie. 2020-01-01 10:45:23
-@param leverage: the amount of leverage to apply to the order as an integer
-@param onConfirm: function called when the bitfinex websocket receives signal that the order
-  was confirmed
-@param onClose: function called when the bitfinex websocket receives signal that the order
-  was closed due to being filled or cancelled
-
-
-## update_order
-```python
-BfxWebsocket.update_order(*args, **kwargs)
-```
-
-Update an existing order
-
-__Attributes__
-
-- `@param orderId`: the id of the order that you want to update
-- `@param price`: the price you want to buy/sell at (must be positive)
-- `@param amount`: order size: how much you want to buy/sell,
-  a negative amount indicates a sell order and positive a buy order
-- `@param delta`:	change of amount
-- `@param price_trailing`:	decimal trailing price
-- `@param price_aux_limit`:	decimal	auxiliary Limit price (only for STOP LIMIT)
-- `@param hidden`: if True, order should be hidden from orderbooks
-- `@param close`: if True, close position if position present
-- `@param reduce_only`: if True, ensures that the executed order does not flip the opened position
-- `@param post_only`: if True, ensures the limit order will be added to the order book and not
-  match with a pre-existing order
-- `@param time_in_force`: datetime for automatic order cancellation ie. 2020-01-01 10:45:23
-- `@param leverage`: the amount of leverage to apply to the order as an integer
-- `@param onConfirm`: function called when the bitfinex websocket receives signal that the order
-  was confirmed
-- `@param onClose`: function called when the bitfinex websocket receives signal that the order
-  was closed due to being filled or cancelled
-
-
-## cancel_order
-```python
-BfxWebsocket.cancel_order(*args, **kwargs)
-```
-
-Cancel an existing open order
-
-__Attributes__
-
-- `@param orderId`: the id of the order that you want to update
-- `@param onConfirm`: function called when the bitfinex websocket receives signal that the
-                  order
-  was confirmed
-- `@param onClose`: function called when the bitfinex websocket receives signal that the order
-  was closed due to being filled or cancelled
-
-
-## cancel_order_group
-```python
-BfxWebsocket.cancel_order_group(*args, **kwargs)
-```
-
-Cancel a set of orders using a single group id.
-
-
-## cancel_all_orders
-```python
-BfxWebsocket.cancel_all_orders(*args, **kwargs)
-```
-
-Cancel all existing open orders
-
-This function closes all open orders.
-
-
-## cancel_order_multi
-```python
-BfxWebsocket.cancel_order_multi(*args, **kwargs)
-```
-
-Cancel existing open orders as a batch
-
-__Attributes__
-
-- `@param ids`: an array of order ids
-- `@param gids`: an array of group ids
-
diff --git a/examples/rest/authenticated/claim_position.py b/examples/rest/authenticated/claim_position.py
new file mode 100644
index 00000000..5dbe89a6
--- /dev/null
+++ b/examples/rest/authenticated/claim_position.py
@@ -0,0 +1,19 @@
+# python -c "import examples.rest.authenticated.claim_position"
+
+import os
+
+from bfxapi import Client, REST_HOST
+
+from bfxapi.rest.types import Notification, PositionClaim
+
+bfx = Client(
+    rest_host=REST_HOST,
+    api_key=os.getenv("BFX_API_KEY"),
+    api_secret=os.getenv("BFX_API_SECRET")
+)
+
+# Claims all active positions
+for position in bfx.rest.auth.get_positions():
+    notification: Notification[PositionClaim] = bfx.rest.auth.claim_position(position.position_id)
+    claim: PositionClaim = notification.data
+    print(f"Position: {position} | PositionClaim: {claim}")
diff --git a/examples/rest/authenticated/get_wallets.py b/examples/rest/authenticated/get_wallets.py
new file mode 100644
index 00000000..1a2d1057
--- /dev/null
+++ b/examples/rest/authenticated/get_wallets.py
@@ -0,0 +1,44 @@
+# python -c "import examples.rest.authenticated.get_wallets"
+
+import os
+
+from bfxapi import Client, REST_HOST
+
+from bfxapi.rest.types import List, Wallet, Transfer, \
+    DepositAddress, LightningNetworkInvoice, Withdrawal, \
+        Notification
+
+bfx = Client(
+    rest_host=REST_HOST,
+    api_key=os.getenv("BFX_API_KEY"),
+    api_secret=os.getenv("BFX_API_SECRET")
+)
+
+# Gets all user's available wallets
+wallets: List[Wallet] = bfx.rest.auth.get_wallets()
+
+# Transfers funds (0.001 ETH) from exchange wallet to funding wallet
+A: Notification[Transfer] = bfx.rest.auth.transfer_between_wallets(
+    from_wallet="exchange", to_wallet="funding", currency="ETH",
+        currency_to="ETH", amount=0.001)
+
+print("Transfer:", A.data)
+
+# Retrieves the deposit address for bitcoin currency in exchange wallet.
+B: Notification[DepositAddress] = bfx.rest.auth.get_deposit_address(
+    wallet="exchange", method="bitcoin", renew=False)
+
+print("Deposit address:", B.data)
+
+# Generates a lightning network deposit invoice
+C: Notification[LightningNetworkInvoice] = bfx.rest.auth.generate_deposit_invoice(
+    wallet="funding", currency="LNX", amount=0.001)
+
+print("Lightning network invoice:", C.data)
+
+# Withdraws 1.0 UST from user's exchange wallet to address 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
+D: Notification[Withdrawal] = bfx.rest.auth.submit_wallet_withdrawal(
+    wallet="exchange", method="tetheruse", address="0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
+        amount=1.0)
+
+print("Withdrawal:", D.data)
diff --git a/examples/rest/authenticated/set_derivative_position_collateral.py b/examples/rest/authenticated/set_derivative_position_collateral.py
new file mode 100644
index 00000000..0949cac2
--- /dev/null
+++ b/examples/rest/authenticated/set_derivative_position_collateral.py
@@ -0,0 +1,36 @@
+# python -c "import examples.rest.authenticated.set_derivatives_position_collateral"
+
+import os
+
+from bfxapi import Client, REST_HOST
+
+from bfxapi.rest.types import DerivativePositionCollateral, DerivativePositionCollateralLimits
+
+bfx = Client(
+    rest_host=REST_HOST,
+    api_key=os.getenv("BFX_API_KEY"),
+    api_secret=os.getenv("BFX_API_SECRET")
+)
+
+submit_order_notification = bfx.rest.auth.submit_order(
+    type="LIMIT",
+    symbol="tBTCF0:USTF0",
+    amount="0.015",
+    price="16700",
+    lev=10
+)
+
+print("New Order:", submit_order_notification.data)
+
+# Update the amount of collateral for tBTCF0:USTF0 derivative position
+derivative_position_collateral: DerivativePositionCollateral = \
+    bfx.rest.auth.set_derivative_position_collateral(symbol="tBTCF0:USTF0", collateral=50.0)
+
+print("Status:", bool(derivative_position_collateral.status))
+
+# Calculate the minimum and maximum collateral that can be assigned to tBTCF0:USTF0.
+derivative_position_collateral_limits: DerivativePositionCollateralLimits = \
+    bfx.rest.auth.get_derivative_position_collateral_limits(symbol="tBTCF0:USTF0")
+
+print(f"Minimum collateral: {derivative_position_collateral_limits.min_collateral} | " \
+    f"Maximum collateral: {derivative_position_collateral_limits.max_collateral}")
diff --git a/examples/rest/authenticated/submit_funding_offer.py b/examples/rest/authenticated/submit_funding_offer.py
new file mode 100644
index 00000000..8291e67c
--- /dev/null
+++ b/examples/rest/authenticated/submit_funding_offer.py
@@ -0,0 +1,30 @@
+# python -c "import examples.rest.authenticated.submit_funding_offer"
+
+import os
+
+from bfxapi import Client, REST_HOST
+from bfxapi.enums import FundingOfferType, Flag
+from bfxapi.rest.types import Notification, FundingOffer
+
+bfx = Client(
+    rest_host=REST_HOST,
+    api_key=os.getenv("BFX_API_KEY"),
+    api_secret=os.getenv("BFX_API_SECRET")
+)
+
+# Submit a new funding offer
+notification: Notification[FundingOffer] = bfx.rest.auth.submit_funding_offer(
+    type=FundingOfferType.LIMIT,
+    symbol="fUSD",
+    amount=123.45,
+    rate=0.001,
+    period=2,
+    flags=Flag.HIDDEN
+)
+
+print("Funding Offer notification:", notification)
+
+# Get all fUSD active funding offers
+offers = bfx.rest.auth.get_funding_offers(symbol="fUSD")
+
+print("Offers (fUSD):", offers)
diff --git a/examples/rest/authenticated/submit_order.py b/examples/rest/authenticated/submit_order.py
new file mode 100644
index 00000000..1481de07
--- /dev/null
+++ b/examples/rest/authenticated/submit_order.py
@@ -0,0 +1,42 @@
+# python -c "import examples.rest.authenticated.submit_order"
+
+import os
+
+from bfxapi import Client, REST_HOST
+from bfxapi.enums import OrderType, Flag
+from bfxapi.rest.types import Notification, Order
+
+bfx = Client(
+    rest_host=REST_HOST,
+    api_key=os.getenv("BFX_API_KEY"),
+    api_secret=os.getenv("BFX_API_SECRET")
+)
+
+# Submit a new order
+submit_order_notification: Notification[Order] = bfx.rest.auth.submit_order(
+    type=OrderType.EXCHANGE_LIMIT,
+    symbol="tBTCUST",
+    amount=0.015,
+    price=10000,
+    flags=Flag.HIDDEN + Flag.OCO + Flag.CLOSE
+)
+
+print("Submit order notification:", submit_order_notification)
+
+order: Order = submit_order_notification.data
+
+# Update its amount and its price
+update_order_notification: Notification[Order] = bfx.rest.auth.update_order(
+    id=order.id,
+    amount=0.020,
+    price=10150
+)
+
+print("Update order notification:", update_order_notification)
+
+# Cancel it by its ID
+cancel_order_notification: Notification[Order] = bfx.rest.auth.cancel_order(
+    id=order.id
+)
+
+print("Cancel order notification:", cancel_order_notification)
diff --git a/examples/rest/authenticated/toggle_keep_funding.py b/examples/rest/authenticated/toggle_keep_funding.py
new file mode 100644
index 00000000..4f924aec
--- /dev/null
+++ b/examples/rest/authenticated/toggle_keep_funding.py
@@ -0,0 +1,24 @@
+# python -c "import examples.rest.authenticated.toggle_keep_funding"
+
+import os
+
+from bfxapi import Client, REST_HOST
+
+from bfxapi.rest.types import List, FundingLoan, Notification
+
+bfx = Client(
+    rest_host=REST_HOST,
+    api_key=os.getenv("BFX_API_KEY"),
+    api_secret=os.getenv("BFX_API_SECRET")
+)
+
+loans: List[FundingLoan] = bfx.rest.auth.get_funding_loans(symbol="fUSD")
+
+# Set every loan's keep funding status to <off> (1: <on>, 2: <off>)
+notification: Notification[None] = bfx.rest.auth.toggle_keep_funding(
+    type="loan",
+    ids=[ loan.id for loan in loans ],
+    changes={ loan.id: 2 for loan in loans }
+)
+
+print("Toggle keep funding notification:", notification)
diff --git a/examples/rest/merchant/settings.py b/examples/rest/merchant/settings.py
new file mode 100644
index 00000000..4f974b74
--- /dev/null
+++ b/examples/rest/merchant/settings.py
@@ -0,0 +1,28 @@
+# python -c "import examples.rest.merchant.settings"
+
+import os
+
+from bfxapi import Client, REST_HOST
+
+from bfxapi.rest.enums import MerchantSettingsKey
+
+bfx = Client(
+    rest_host=REST_HOST,
+    api_key=os.getenv("BFX_API_KEY"),
+    api_secret=os.getenv("BFX_API_SECRET")
+)
+
+if not bfx.rest.merchant.set_merchant_settings(MerchantSettingsKey.RECOMMEND_STORE, 1):
+    print(f"Cannot set <{MerchantSettingsKey.RECOMMEND_STORE}> to <1>.")
+
+print(f"The current <{MerchantSettingsKey.PREFERRED_FIAT}> value is:",
+    bfx.rest.merchant.get_merchant_settings(MerchantSettingsKey.PREFERRED_FIAT))
+
+settings = bfx.rest.merchant.list_merchant_settings([
+    MerchantSettingsKey.DUST_BALANCE_UI,
+    MerchantSettingsKey.MERCHANT_CUSTOMER_SUPPORT_URL,
+    MerchantSettingsKey.MERCHANT_UNDERPAID_THRESHOLD
+])
+
+for key, value in settings.items():
+    print(f"<{key}>:", value)
diff --git a/examples/rest/merchant/submit_invoice.py b/examples/rest/merchant/submit_invoice.py
new file mode 100644
index 00000000..7d7a110b
--- /dev/null
+++ b/examples/rest/merchant/submit_invoice.py
@@ -0,0 +1,45 @@
+# python -c "import examples.rest.merchant.submit_invoice"
+
+import os
+
+from bfxapi import Client, REST_HOST
+
+from bfxapi.rest.types import InvoiceSubmission
+
+bfx = Client(
+    rest_host=REST_HOST,
+    api_key=os.getenv("BFX_API_KEY"),
+    api_secret=os.getenv("BFX_API_SECRET")
+)
+
+customer_info = {
+    "nationality": "DE",
+    "residCountry": "GB",
+    "residCity": "London",
+    "residZipCode": "WC2H 7NA",
+    "residStreet": "5-6 Leicester Square",
+    "residBuildingNo": "23 A",
+    "fullName": "John Doe",
+    "email": "john@example.com"
+}
+
+invoice: InvoiceSubmission = bfx.rest.merchant.submit_invoice(
+    amount=1.0,
+    currency="USD",
+    order_id="test",
+    customer_info=customer_info,
+    pay_currencies=["ETH"],
+    duration=86400 * 10
+)
+
+print("Invoice submission:", invoice)
+
+print(bfx.rest.merchant.complete_invoice(
+    id=invoice.id,
+    pay_currency="ETH",
+    deposit_id=1
+))
+
+print(bfx.rest.merchant.get_invoices(limit=25))
+
+print(bfx.rest.merchant.get_invoices_paginated(page=1, page_size=60, sort="asc", sort_field="t"))
diff --git a/examples/rest/public/book.py b/examples/rest/public/book.py
new file mode 100644
index 00000000..f716fc49
--- /dev/null
+++ b/examples/rest/public/book.py
@@ -0,0 +1,24 @@
+# python -c "import examples.rest.public.book"
+
+from bfxapi import Client, PUB_REST_HOST
+
+from bfxapi.rest.types import List, TradingPairBook, TradingPairRawBook, \
+    FundingCurrencyBook, FundingCurrencyRawBook
+
+bfx = Client(rest_host=PUB_REST_HOST)
+
+t_book: List[TradingPairBook] = bfx.rest.public.get_t_book("tBTCUSD", precision="P0", len=25)
+
+print("25 price points of tBTCUSD order book (with precision P0):", t_book)
+
+t_raw_book: List[TradingPairRawBook] = bfx.rest.public.get_t_raw_book("tBTCUSD")
+
+print("tBTCUSD raw order book:", t_raw_book)
+
+f_book: List[FundingCurrencyBook] = bfx.rest.public.get_f_book("fUSD", precision="P0", len=25)
+
+print("25 price points of fUSD order book (with precision P0):", f_book)
+
+f_raw_book: List[FundingCurrencyRawBook] = bfx.rest.public.get_f_raw_book("fUSD")
+
+print("fUSD raw order book:", f_raw_book)
diff --git a/examples/rest/public/conf.py b/examples/rest/public/conf.py
new file mode 100644
index 00000000..431eb26d
--- /dev/null
+++ b/examples/rest/public/conf.py
@@ -0,0 +1,18 @@
+# python -c "import examples.rest.public.conf"
+
+from bfxapi import Client, PUB_REST_HOST
+
+from bfxapi.rest.enums import Config
+
+bfx = Client(rest_host=PUB_REST_HOST)
+
+print("Available configs:", [ config.value for config in Config ])
+
+# Prints a map from symbols to their API symbols (pub:map:currency:sym)
+print (bfx.rest.public.conf(Config.MAP_CURRENCY_SYM))
+
+# Prints all the available exchange trading pairs (pub:list:pair:exchange)
+print(bfx.rest.public.conf(Config.LIST_PAIR_EXCHANGE))
+
+# Prints all the available funding currencies (pub:list:currency)
+print(bfx.rest.public.conf(Config.LIST_CURRENCY))
diff --git a/examples/rest/public/get_candles_hist.py b/examples/rest/public/get_candles_hist.py
new file mode 100644
index 00000000..12588b1b
--- /dev/null
+++ b/examples/rest/public/get_candles_hist.py
@@ -0,0 +1,11 @@
+# python -c "import examples.rest.public.get_candles_hist"
+
+from bfxapi import Client, PUB_REST_HOST
+
+bfx = Client(rest_host=PUB_REST_HOST)
+
+print(f"Candles: {bfx.rest.public.get_candles_hist(symbol='tBTCUSD')}")
+
+# Be sure to specify a period or aggregated period when retrieving funding candles.
+# If you wish to mimic the candles found in the UI, use the following setup to aggregate all funding candles: a30:p2:p30
+print(f"Candles: {bfx.rest.public.get_candles_hist(tf='15m', symbol='fUSD:a30:p2:p30')}")
diff --git a/examples/rest/public/pulse_endpoints.py b/examples/rest/public/pulse_endpoints.py
new file mode 100644
index 00000000..d2305ac7
--- /dev/null
+++ b/examples/rest/public/pulse_endpoints.py
@@ -0,0 +1,24 @@
+# python -c "import examples.rest.public.pulse_endpoints"
+
+import datetime
+
+from bfxapi import Client, PUB_REST_HOST
+
+from bfxapi.rest.types import List, PulseMessage, PulseProfile
+
+bfx = Client(rest_host=PUB_REST_HOST)
+
+# POSIX timestamp in milliseconds (check https://currentmillis.com/)
+end = datetime.datetime(2020, 5, 2).timestamp() * 1000
+
+# Retrieves 25 pulse messages up to 2020/05/02
+messages: List[PulseMessage] = bfx.rest.public.get_pulse_message_history(end=end, limit=25)
+
+for message in messages:
+    print(f"Message author: {message.profile.nickname} ({message.profile.puid})")
+    print(f"Title: <{message.title}>")
+    print(f"Tags: {message.tags}\n")
+
+profile: PulseProfile = bfx.rest.public.get_pulse_profile_details("News")
+URL = profile.picture.replace("size", "small")
+print(f"<{profile.nickname}>'s profile picture: https://s3-eu-west-1.amazonaws.com/bfx-pub/{URL}")
diff --git a/examples/rest/public/rest_calculation_endpoints.py b/examples/rest/public/rest_calculation_endpoints.py
new file mode 100644
index 00000000..e66ca70f
--- /dev/null
+++ b/examples/rest/public/rest_calculation_endpoints.py
@@ -0,0 +1,28 @@
+# python -c "import examples.rest.public.rest_calculation_endpoints"
+
+from bfxapi import Client, PUB_REST_HOST
+
+from bfxapi.rest.types import TradingMarketAveragePrice, FundingMarketAveragePrice, FxRate
+
+bfx = Client(rest_host=PUB_REST_HOST)
+
+trading_market_average_price: TradingMarketAveragePrice = bfx.rest.public.get_trading_market_average_price(
+    symbol="tBTCUSD",
+    amount=-100,
+    price_limit=20000.5
+)
+
+print("Average execution price for tBTCUSD:", trading_market_average_price.price_avg)
+
+funding_market_average_price: FundingMarketAveragePrice = bfx.rest.public.get_funding_market_average_price(
+    symbol="fUSD",
+    amount=100,
+    period=2,
+    rate_limit=0.00015
+)
+
+print("Average execution rate for fUSD:", funding_market_average_price.rate_avg)
+
+fx_rate: FxRate = bfx.rest.public.get_fx_rate(ccy1="USD", ccy2="EUR")
+
+print("Exchange rate between USD and EUR:", fx_rate.current_rate)
diff --git a/examples/rest/public/trades.py b/examples/rest/public/trades.py
new file mode 100644
index 00000000..5e9242a1
--- /dev/null
+++ b/examples/rest/public/trades.py
@@ -0,0 +1,17 @@
+# python -c "import examples.rest.public.trades"
+
+from bfxapi import Client, PUB_REST_HOST
+from bfxapi.rest.enums import Sort
+from bfxapi.rest.types import List, TradingPairTrade,  FundingCurrencyTrade
+
+bfx = Client(rest_host=PUB_REST_HOST)
+
+t_trades: List[TradingPairTrade] = bfx.rest.public.get_t_trades("tBTCUSD", \
+    limit=15, sort=Sort.ASCENDING)
+
+print("Latest 15 trades for tBTCUSD (in ascending order):", t_trades)
+
+f_trades: List[FundingCurrencyTrade] = bfx.rest.public.get_f_trades("fUSD", \
+    limit=15, sort=Sort.DESCENDING)
+
+print("Latest 15 trades for fUSD (in descending order):", f_trades)
diff --git a/examples/websocket/authenticated/submit_order.py b/examples/websocket/authenticated/submit_order.py
new file mode 100644
index 00000000..55554930
--- /dev/null
+++ b/examples/websocket/authenticated/submit_order.py
@@ -0,0 +1,44 @@
+# python -c "import examples.websocket.authenticated.submit_order"
+
+import os
+
+from bfxapi import Client, WSS_HOST
+from bfxapi.enums import Error, OrderType
+from bfxapi.websocket.types import Notification, Order
+
+bfx = Client(
+    wss_host=WSS_HOST,
+    api_key=os.getenv("BFX_API_KEY"),
+    api_secret=os.getenv("BFX_API_SECRET")
+)
+
+@bfx.wss.on("wss-error")
+def on_wss_error(code: Error, msg: str):
+    print(code, msg)
+
+@bfx.wss.on("authenticated")
+async def on_authenticated(event):
+    print(f"Authentication: {event}")
+
+    await bfx.wss.inputs.submit_order(
+        type=OrderType.EXCHANGE_LIMIT,
+        symbol="tBTCUSD",
+        amount="0.1",
+        price="10000.0"
+    )
+
+    print("The order has been sent.")
+
+@bfx.wss.on("on-req-notification")
+async def on_notification(notification: Notification[Order]):
+    print(f"Notification: {notification}.")
+
+@bfx.wss.on("order_new")
+async def on_order_new(order_new: Order):
+    print(f"Order new: {order_new}")
+
+@bfx.wss.on("subscribed")
+def on_subscribed(subscription):
+    print(f"Subscription successful for <{subscription}>.")
+
+bfx.wss.run()
diff --git a/examples/websocket/authenticated/wallets.py b/examples/websocket/authenticated/wallets.py
new file mode 100644
index 00000000..7dcb58c6
--- /dev/null
+++ b/examples/websocket/authenticated/wallets.py
@@ -0,0 +1,30 @@
+# python -c "import examples.websocket.authenticated.wallets"
+
+import os
+
+from bfxapi import Client
+from bfxapi.enums import Error
+from bfxapi.websocket.types import List, Wallet
+
+bfx = Client(
+    api_key=os.getenv("BFX_API_KEY"),
+    api_secret=os.getenv("BFX_API_SECRET"),
+    filters=["wallet"]
+)
+
+@bfx.wss.on("wss-error")
+def on_wss_error(code: Error, msg: str):
+    print(code, msg)
+
+@bfx.wss.on("wallet_snapshot")
+def on_wallet_snapshot(wallets: List[Wallet]):
+    for wallet in wallets:
+        print(f"Wallet: {wallet.wallet_type} | {wallet.currency}")
+        print(f"Available balance: {wallet.available_balance}")
+        print(f"Wallet trade details: {wallet.trade_details}")
+
+@bfx.wss.on("wallet_update")
+def on_wallet_update(wallet: Wallet):
+    print(f"Wallet update: {wallet}")
+
+bfx.wss.run()
diff --git a/examples/websocket/public/derivatives_status.py b/examples/websocket/public/derivatives_status.py
new file mode 100644
index 00000000..3609cff8
--- /dev/null
+++ b/examples/websocket/public/derivatives_status.py
@@ -0,0 +1,23 @@
+# python -c "import examples.websocket.public.derivatives_status"
+
+from bfxapi import Client, PUB_WSS_HOST
+from bfxapi.websocket.enums import Error, Channel
+from bfxapi.websocket.types import DerivativesStatus
+
+from bfxapi.websocket import subscriptions
+
+bfx = Client(wss_host=PUB_WSS_HOST)
+
+@bfx.wss.on("derivatives_status_update")
+def on_derivatives_status_update(subscription: subscriptions.Status, data: DerivativesStatus):
+    print(f"{subscription}:", data)
+
+@bfx.wss.on("wss-error")
+def on_wss_error(code: Error, msg: str):
+    print(code, msg)
+
+@bfx.wss.once("open")
+async def on_open():
+    await bfx.wss.subscribe(Channel.STATUS, key="deriv:tBTCF0:USTF0")
+
+bfx.wss.run()
diff --git a/examples/websocket/public/order_book.py b/examples/websocket/public/order_book.py
new file mode 100644
index 00000000..aef1d04c
--- /dev/null
+++ b/examples/websocket/public/order_book.py
@@ -0,0 +1,65 @@
+# python -c "import examples.websocket.public.order_book"
+
+from collections import OrderedDict
+
+from typing import List
+
+from bfxapi import Client, PUB_WSS_HOST
+
+from bfxapi.websocket import subscriptions
+from bfxapi.websocket.enums import Channel, Error
+from bfxapi.websocket.types import TradingPairBook
+
+class OrderBook:
+    def __init__(self, symbols: List[str]):
+        self.__order_book = {
+            symbol: {
+                "bids": OrderedDict(), "asks": OrderedDict() 
+            } for symbol in symbols
+        }
+
+    def update(self, symbol: str, data: TradingPairBook) -> None:
+        price, count, amount = data.price, data.count, data.amount
+
+        kind = "bids" if amount > 0 else "asks"
+
+        if count > 0:
+            self.__order_book[symbol][kind][price] = {
+                "price": price, 
+                "count": count,
+                "amount": amount 
+            }
+
+        if count == 0:
+            if price in self.__order_book[symbol][kind]:
+                del self.__order_book[symbol][kind][price]
+
+SYMBOLS = [ "tBTCUSD", "tLTCUSD", "tLTCBTC", "tETHUSD", "tETHBTC" ]
+
+order_book = OrderBook(symbols=SYMBOLS)
+
+bfx = Client(wss_host=PUB_WSS_HOST)
+
+@bfx.wss.on("wss-error")
+def on_wss_error(code: Error, msg: str):
+    print(code, msg)
+
+@bfx.wss.on("open")
+async def on_open():
+    for symbol in SYMBOLS:
+        await bfx.wss.subscribe(Channel.BOOK, symbol=symbol)
+
+@bfx.wss.on("subscribed")
+def on_subscribed(subscription):
+    print(f"Subscription successful for pair <{subscription['pair']}>")
+
+@bfx.wss.on("t_book_snapshot")
+def on_t_book_snapshot(subscription: subscriptions.Book, snapshot: List[TradingPairBook]):
+    for data in snapshot:
+        order_book.update(subscription["symbol"], data)
+
+@bfx.wss.on("t_book_update")
+def on_t_book_update(subscription: subscriptions.Book, data: TradingPairBook):
+    order_book.update(subscription["symbol"], data)
+
+bfx.wss.run()
diff --git a/examples/websocket/public/raw_order_book.py b/examples/websocket/public/raw_order_book.py
new file mode 100644
index 00000000..07814af0
--- /dev/null
+++ b/examples/websocket/public/raw_order_book.py
@@ -0,0 +1,65 @@
+# python -c "import examples.websocket.public.raw_order_book"
+
+from collections import OrderedDict
+
+from typing import List
+
+from bfxapi import Client, PUB_WSS_HOST
+
+from bfxapi.websocket import subscriptions
+from bfxapi.websocket.enums import Channel, Error
+from bfxapi.websocket.types import TradingPairRawBook
+
+class RawOrderBook:
+    def __init__(self, symbols: List[str]):
+        self.__raw_order_book = {
+            symbol: {
+                "bids": OrderedDict(), "asks": OrderedDict() 
+            } for symbol in symbols
+        }
+
+    def update(self, symbol: str, data: TradingPairRawBook) -> None:
+        order_id, price, amount = data.order_id, data.price, data.amount
+
+        kind = "bids" if amount > 0 else "asks"
+
+        if price > 0:
+            self.__raw_order_book[symbol][kind][order_id] = {
+                "order_id": order_id,
+                "price": price, 
+                "amount": amount 
+            }
+
+        if price == 0:
+            if order_id in self.__raw_order_book[symbol][kind]:
+                del self.__raw_order_book[symbol][kind][order_id]
+
+SYMBOLS = [ "tBTCUSD", "tLTCUSD", "tLTCBTC", "tETHUSD", "tETHBTC" ]
+
+raw_order_book = RawOrderBook(symbols=SYMBOLS)
+
+bfx = Client(wss_host=PUB_WSS_HOST)
+
+@bfx.wss.on("wss-error")
+def on_wss_error(code: Error, msg: str):
+    print(code, msg)
+
+@bfx.wss.on("open")
+async def on_open():
+    for symbol in SYMBOLS:
+        await bfx.wss.subscribe(Channel.BOOK, symbol=symbol, prec="R0")
+
+@bfx.wss.on("subscribed")
+def on_subscribed(subscription):
+    print(f"Subscription successful for pair <{subscription['pair']}>")
+
+@bfx.wss.on("t_raw_book_snapshot")
+def on_t_raw_book_snapshot(subscription: subscriptions.Book, snapshot: List[TradingPairRawBook]):
+    for data in snapshot:
+        raw_order_book.update(subscription["symbol"], data)
+
+@bfx.wss.on("t_raw_book_update")
+def on_t_raw_book_update(subscription: subscriptions.Book, data: TradingPairRawBook):
+    raw_order_book.update(subscription["symbol"], data)
+
+bfx.wss.run()
diff --git a/examples/websocket/public/ticker.py b/examples/websocket/public/ticker.py
new file mode 100644
index 00000000..c446cd04
--- /dev/null
+++ b/examples/websocket/public/ticker.py
@@ -0,0 +1,21 @@
+# python -c "import examples.websocket.public.ticker"
+
+from bfxapi import Client, PUB_WSS_HOST
+
+from bfxapi.websocket import subscriptions
+from bfxapi.websocket.enums import Channel
+from bfxapi.websocket.types import TradingPairTicker
+
+bfx = Client(wss_host=PUB_WSS_HOST)
+
+@bfx.wss.on("t_ticker_update")
+def on_t_ticker_update(subscription: subscriptions.Ticker, data: TradingPairTicker):
+    print(f"Subscription with subId: {subscription['subId']}")
+
+    print(f"Data: {data}")
+
+@bfx.wss.once("open")
+async def on_open():
+    await bfx.wss.subscribe(Channel.TICKER, symbol="tBTCUSD")
+
+bfx.wss.run()
diff --git a/examples/websocket/public/trades.py b/examples/websocket/public/trades.py
new file mode 100644
index 00000000..9edd20ee
--- /dev/null
+++ b/examples/websocket/public/trades.py
@@ -0,0 +1,29 @@
+# python -c "import examples.websocket.public.trades"
+
+from bfxapi import Client, PUB_WSS_HOST
+from bfxapi.websocket.enums import Error, Channel
+from bfxapi.websocket.types import Candle, TradingPairTrade
+
+from bfxapi.websocket import subscriptions
+
+bfx = Client(wss_host=PUB_WSS_HOST)
+
+@bfx.wss.on("candles_update")
+def on_candles_update(_sub: subscriptions.Candles, candle: Candle):
+    print(f"New candle: {candle}")
+
+@bfx.wss.on("t_trade_executed")
+def on_t_trade_executed(_sub: subscriptions.Trades, trade: TradingPairTrade):
+    print(f"New trade: {trade}")
+
+@bfx.wss.on("wss-error")
+def on_wss_error(code: Error, msg: str):
+    print(code, msg)
+
+@bfx.wss.once("open")
+async def on_open():
+    await bfx.wss.subscribe(Channel.CANDLES, key="trade:1m:tBTCUSD")
+
+    await bfx.wss.subscribe(Channel.TRADES, symbol="tBTCUSD")
+
+bfx.wss.run()
diff --git a/pylint.rc b/pylint.rc
deleted file mode 100644
index ce75f1f4..00000000
--- a/pylint.rc
+++ /dev/null
@@ -1,13 +0,0 @@
-[MESSAGES CONTROL]
-
-disable=too-few-public-methods,
-        import-error,
-        too-many-arguments,
-        duplicate-code,
-        too-many-locals,
-        no-init,
-        len-as-condition,
-        too-many-instance-attributes,
-        invalid-name
-
-ignore=tests,websockets,rest,utils
diff --git a/requirements.txt b/requirements.txt
index 5bb3aee3..b2a3b768 100644
Binary files a/requirements.txt and b/requirements.txt differ
diff --git a/setup.py b/setup.py
index 5a18c151..b8484a6c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,60 +1,45 @@
-"""A setuptools based setup module.
-See:
-https://packaging.python.org/guides/distributing-packages-using-setuptools/
-https://github.com/pypa/sampleproject
-"""
+from distutils.core import setup
 
-# Always prefer setuptools over distutils
-from setuptools import setup, find_packages
-from os import path
+version = {}
+with open("bfxapi/version.py", encoding="utf-8") as fp:
+    exec(fp.read(), version) #pylint: disable=exec-used
 
-here = path.abspath(path.dirname(__file__))
 setup(
-    name='bitfinex-api-py',
-    version='2.0.6',
-    description='Official Bitfinex Python API',
-    long_description='A Python reference implementation of the Bitfinex API for both REST and websocket interaction',
-    long_description_content_type='text/markdown',
-    url='https://github.com/bitfinexcom/bitfinex-api-py',
-    author='Bitfinex',
-    author_email='support@bitfinex.com',
+    name="bitfinex-api-py",
+    version=version["__version__"],
+    description="Official Bitfinex Python API",
+    long_description="A Python reference implementation of the Bitfinex API for both REST and websocket interaction",
+    long_description_content_type="text/markdown",
+    url="https://github.com/bitfinexcom/bitfinex-api-py",
+    author="Bitfinex",
+    author_email="support@bitfinex.com",
+    license="Apache-2.0",
     classifiers=[
-        # How mature is this project? Common values are
-        #   3 - Alpha
-        #   4 - Beta
-        #   5 - Production/Stable
-        'Development Status :: 5 - Production/Stable',
+        "Development Status :: 4 - Beta",
 
-        # Project Audience
-        'Intended Audience :: Developers',
-        'Topic :: Software Development :: Build Tools',
-        # Project License
-        'License :: OSI Approved :: Apache Software License',
+        "Intended Audience :: Developers",
+        "Topic :: Software Development :: Build Tools",
 
-        # Python versions (not enforced)
-        'Programming Language :: Python :: 3.0',
-        'Programming Language :: Python :: 3.2',
-        'Programming Language :: Python :: 3.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',
-    ],
-    keywords='bitfinex,api,trading',
-    packages=find_packages(exclude=['examples', 'tests', 'docs']),
-    # Python versions (enforced)
-    python_requires='>=3.0.0, <4',
-    # deps installed by pip
-    install_requires=[
-        'asyncio~=3.0',
-        'websockets>=8,<10',
-        'aiohttp~=3.0',
-        'pyee~=8.0'
+        "License :: OSI Approved :: Apache-2.0",
+
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
     ],
+    keywords="bitfinex,api,trading",
     project_urls={
-        'Bug Reports': 'https://github.com/bitfinexcom/bitfinex-api-py/issues',
-        'Source': 'https://github.com/bitfinexcom/bitfinex-api-py',
+        "Bug Reports": "https://github.com/bitfinexcom/bitfinex-api-py/issues",
+        "Source": "https://github.com/bitfinexcom/bitfinex-api-py",
     },
+    packages=[
+        "bfxapi", "bfxapi.utils",
+        "bfxapi.websocket", "bfxapi.websocket.client", "bfxapi.websocket.handlers", 
+        "bfxapi.rest", "bfxapi.rest.endpoints", "bfxapi.rest.middleware",
+    ],
+    install_requires=[
+        "pyee~=9.0.4",
+        "websockets~=10.4",
+        "requests~=2.28.1"
+    ],
+    python_requires=">=3.8"
 )


Test patch:
diff --git a/bfxapi/tests/__init__.py b/bfxapi/tests/__init__.py
index e69de29b..df783003 100644
--- a/bfxapi/tests/__init__.py
+++ b/bfxapi/tests/__init__.py
@@ -0,0 +1,17 @@
+import unittest
+
+from .test_rest_serializers import TestRestSerializers
+from .test_websocket_serializers import TestWebsocketSerializers
+from .test_labeler import TestLabeler
+from .test_notification import TestNotification
+
+def suite():
+    return unittest.TestSuite([
+        unittest.makeSuite(TestRestSerializers),
+        unittest.makeSuite(TestWebsocketSerializers),
+        unittest.makeSuite(TestLabeler),
+        unittest.makeSuite(TestNotification),
+    ])
+
+if __name__ == "__main__":
+    unittest.TextTestRunner().run(suite())
diff --git a/bfxapi/tests/helpers.py b/bfxapi/tests/helpers.py
deleted file mode 100644
index 1e761395..00000000
--- a/bfxapi/tests/helpers.py
+++ /dev/null
@@ -1,114 +0,0 @@
-import time
-import json
-import asyncio
-from pyee import EventEmitter
-
-from .. import Client, BfxWebsocket, Socket
-
-def get_now():
-	return int(round(time.time() * 1000))
-
-def ev_worker_override():
-		return EventEmitter()
-
-class StubbedWebsocket(BfxWebsocket):
-
-	def __init__(self, *args, **kwargs):
-		self.sent_items = []
-		self.published_items = []
-		super().__init__(create_event_emitter=ev_worker_override, *args, **kwargs)
-
-	async def _main(self, host):
-		print ("Faking wesocket connection to {}".format(host))
-
-	def _start_new_socket(self):
-		socket = Socket(len(self.sockets))
-		socket.set_connected()
-		socket.ws = self
-		self.sockets[socket.id] = socket
-		return socket.id
-
-	def _wait_for_socket(self, socketId):
-		return
-
-	async def publish(self, data, is_json=True):
-		self.published_items += [{
-		'time': get_now(),
-		'data': data
-		}]
-		# convert to string and push through the websocket
-		data = json.dumps(data) if is_json else data
-		return await self.on_message(0, data)
-  
-	async def publish_auth_confirmation(self):
-		return self.publish({"event":"auth","status":"OK","chanId":0,"userId":269499,"auth_id":"58aa0472-b1a9-4690-8ab8-300d68e66aaf","caps":{"orders":{"read":1,"write":1},"account":{"read":1,"write":0},"funding":{"read":1,"write":1},"history":{"read":1,"write":0},"wallets":{"read":1,"write":1},"withdraw":{"read":0,"write":1},"positions":{"read":1,"write":1}}})
-
-	async def send(self, data_string):
-		self.sent_items += [{
-		'time': get_now(),
-		'data': data_string
-		}]
-
-	def get_published_items(self):
-		return self.published_items
-
-	def get_sent_items(self):
-		return self.sent_items
-
-	def get_last_sent_item(self):
-		return self.sent_items[-1:][0]
-
-	def get_sent_items_count(self):
-		return len(self.sent_items)
-
-class EventWatcher():
-
-	def __init__(self, ws, event):
-		self.value = None
-		self.event = event
-		ws.once(event, self._finish)
-
-	def _finish(self, value):
-		self.value = value or {}
-
-	@classmethod
-	def watch(cls, ws, event):
-		return EventWatcher(ws, event)
-
-	def wait_until_complete(self, max_wait_time=5):
-		counter = 0
-		while self.value == None:
-			if counter > 5:
-				raise Exception('Wait time limit exceeded for event {}'.format(self.event))
-			time.sleep(1)
-			counter += 1
-		return self.value
-
-class StubClient():
-	ws = None
-	res = None
-
-def create_stubbed_client(*args, **kwargs):
-	client = StubClient()
-	# no support for rest stubbing yet
-	client.rest = None
-	wsStub = StubbedWebsocket(*args, **kwargs)
-	# stub client.ws so tests can use publish
-	client.ws = wsStub
-	client.ws.API_KEY = "test key"
-	client.ws.API_SECRET = "secret key"
-	# stub socket so we can track socket send requests
-	socket = Socket(0)
-	socket.set_connected()
-	socket.ws = wsStub
-	client.ws.sockets = { 0: socket }
-	return client
-
-async def ws_publish_auth_accepted(ws):
-	  return await ws.publish({"event":"auth","status":"OK","chanId":0,"userId":269499,"auth_id":"58aa0472-b1a9-4690-8ab8-300d68e66aaf","caps":{"orders":{"read":1,"write":1},"account":{"read":1,"write":0},"funding":{"read":1,"write":1},"history":{"read":1,"write":0},"wallets":{"read":1,"write":1},"withdraw":{"read":0,"write":1},"positions":{"read":1,"write":1}}})
-
-async def ws_publish_connection_init(ws):
-	  return await ws.publish({"event":"info","version":2,"serverId":"748c00f2-250b-46bb-8519-ce1d7d68e4f0","platform":{"status":1}})
-
-async def ws_publish_conf_accepted(ws, flags_code):
-	  return await ws.publish({"event":"conf","status":"OK","flags":flags_code})
diff --git a/bfxapi/tests/test_decimal.py b/bfxapi/tests/test_decimal.py
deleted file mode 100644
index 23adbc19..00000000
--- a/bfxapi/tests/test_decimal.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import sys
-sys.path.append('../components')
-
-from bfxapi import Decimal
-
-def test_precision():
-	assert str(Decimal(0.00000123456789)) == "0.00000123456789"
-	assert str(Decimal("0.00000123456789")) == "0.00000123456789"
-
-def test_float_operations():
-	assert str(Decimal(0.0002) * 0.02) == "0.000004"
-	assert str(0.02 * Decimal(0.0002)) == "0.000004"
-	
-	assert str(Decimal(0.0002) / 0.02) == "0.01"
-	assert str(0.02 / Decimal(0.0002)) == "0.01"
-
-	assert str(0.02 + Decimal(0.0002)) == "0.0202"
-	assert str(Decimal(0.0002) + 0.02) == "0.0202"
-
-	assert str(0.02 - Decimal(0.0002)) == "-0.0198"
-	assert str(Decimal(0.0002) - 0.02) == "-0.0198"
-
-	assert str(0.01 // Decimal(0.0004)) == "0"
-	assert str(Decimal(0.0004) // 0.01) == "0"
diff --git a/bfxapi/tests/test_labeler.py b/bfxapi/tests/test_labeler.py
new file mode 100644
index 00000000..c3757988
--- /dev/null
+++ b/bfxapi/tests/test_labeler.py
@@ -0,0 +1,56 @@
+import unittest
+
+from typing import Optional
+
+from dataclasses import dataclass
+from ..exceptions import LabelerSerializerException
+from ..labeler import _Type, generate_labeler_serializer, generate_recursive_serializer
+
+class TestLabeler(unittest.TestCase):
+    def test_generate_labeler_serializer(self):
+        @dataclass
+        class Test(_Type):
+            A: Optional[int]
+            B: float
+            C: str
+
+        labels = [ "A", "_PLACEHOLDER", "B", "_PLACEHOLDER", "C" ]
+
+        serializer = generate_labeler_serializer("Test", Test, labels)
+
+        self.assertEqual(serializer.parse(5, None, 65.0, None, "X"), Test(5, 65.0, "X"),
+            msg="_Serializer should produce the right result.")
+
+        self.assertListEqual(serializer.get_labels(), [ "A", "B", "C" ],
+            msg="_Serializer::get_labels() should return the right list of labels.")
+
+        with self.assertRaises(LabelerSerializerException,
+                msg="_Serializer should raise LabelerSerializerException if given " \
+                        "fewer arguments than the serializer labels."):
+            serializer.parse(5, 65.0, "X")
+
+    def test_generate_recursive_serializer(self):
+        @dataclass
+        class Outer(_Type):
+            A: int
+            B: float
+            C: "Middle"
+
+        @dataclass
+        class Middle(_Type):
+            D: str
+            E: "Inner"
+
+        @dataclass
+        class Inner(_Type):
+            F: bool
+
+        inner = generate_labeler_serializer("Inner", Inner, ["F"])
+        middle = generate_recursive_serializer("Middle", Middle, ["D", "E"], serializers={ "E": inner })
+        outer = generate_recursive_serializer("Outer", Outer, ["A", "B", "C"], serializers={ "C": middle })
+
+        self.assertEqual(outer.parse(10, 45.5, [ "Y", [ True ] ]), Outer(10, 45.5, Middle("Y", Inner(True))),
+            msg="_RecursiveSerializer should produce the right result.")
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/bfxapi/tests/test_notification.py b/bfxapi/tests/test_notification.py
new file mode 100644
index 00000000..0d3cb596
--- /dev/null
+++ b/bfxapi/tests/test_notification.py
@@ -0,0 +1,29 @@
+import unittest
+
+from dataclasses import dataclass
+from ..labeler import generate_labeler_serializer
+from ..notification import _Type, _Notification, Notification
+
+class TestNotification(unittest.TestCase):
+    def test_notification(self):
+        @dataclass
+        class Test(_Type):
+            A: int
+            B: float
+            C: str
+
+        test = generate_labeler_serializer("Test", Test,
+            [ "A", "_PLACEHOLDER", "B", "_PLACEHOLDER", "C" ])
+
+        notification = _Notification[Test](test)
+
+        actual = notification.parse(*[ 1675787861506, "test", None, None, [ 5, None, 65.0, None, "X" ], \
+                0, "SUCCESS", "This is just a test notification." ])
+
+        expected = Notification[Test](1675787861506, "test", None, Test(5, 65.0, "X"),
+                0, "SUCCESS", "This is just a test notification.")
+
+        self.assertEqual(actual, expected, msg="_Notification should produce the right notification.")
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/bfxapi/tests/test_rest_get_public_trades.py b/bfxapi/tests/test_rest_get_public_trades.py
deleted file mode 100644
index 4f7a0d48..00000000
--- a/bfxapi/tests/test_rest_get_public_trades.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import asyncio
-import concurrent.futures
-from bfxapi import Client
-
-bfx = Client(logLevel='DEBUG')
-
-async def run():
-    start = 1617519600000
-    candles = await bfx.rest.get_public_candles(start=start, symbol='tBTCUSD', end=None, tf='1h', sort=1, limit=1)
-    candle = candles[0]
-    price = candle[1]
-    assert price == 57394.61698309
-
-    orders_ids = []
-    trades = await bfx.rest.get_public_trades(start=1617519600000, limit=5, symbol='tBTCUSD', end=None, sort=1)
-    print(trades)
-    for trade in trades:
-        orders_ids.append(trade[0])
-    assert orders_ids == [657815316, 657815314, 657815312, 657815308, 657815304]
-
-    # check that strictly decreasing order id condition is always respected
-    # check that not increasing timestamp condition is always respected
-    orders_ids = []
-    timestamps = []
-    trades = await bfx.rest.get_public_trades(start=1617519600000, limit=5000, symbol='tLEOUSD', end=None, sort=1)
-    print(trades)
-    for trade in trades:
-        orders_ids.append(trade[0])
-        timestamps.append(trade[1])
-
-    assert not all(x > y for x, y in zip(orders_ids, orders_ids[1:])) is False
-    assert not all(x >= y for x, y in zip(orders_ids, orders_ids[1:])) is False
-
-def test_get_public_trades():
-    t = asyncio.ensure_future(run())
-    asyncio.get_event_loop().run_until_complete(t)
diff --git a/bfxapi/tests/test_rest_serializers.py b/bfxapi/tests/test_rest_serializers.py
new file mode 100644
index 00000000..c9c18860
--- /dev/null
+++ b/bfxapi/tests/test_rest_serializers.py
@@ -0,0 +1,21 @@
+#pylint: disable=duplicate-code
+
+import unittest
+
+from ..labeler import _Type
+
+from ..rest import serializers
+
+class TestRestSerializers(unittest.TestCase):
+    def test_rest_serializers(self):
+        for serializer in map(serializers.__dict__.get, serializers.__serializers__):
+            self.assertTrue(issubclass(serializer.klass, _Type),
+                f"_Serializer <{serializer.name}>: .klass field must be a subclass " \
+                    f"of _Type (got {serializer.klass}).")
+
+            self.assertListEqual(serializer.get_labels(), list(serializer.klass.__annotations__),
+                f"_Serializer <{serializer.name}> and _Type <{serializer.klass.__name__}> " \
+                    "must have matching labels and fields.")
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/bfxapi/tests/test_websocket_serializers.py b/bfxapi/tests/test_websocket_serializers.py
new file mode 100644
index 00000000..b4338688
--- /dev/null
+++ b/bfxapi/tests/test_websocket_serializers.py
@@ -0,0 +1,21 @@
+#pylint: disable=duplicate-code
+
+import unittest
+
+from ..labeler import _Type
+
+from ..websocket import serializers
+
+class TestWebsocketSerializers(unittest.TestCase):
+    def test_websocket_serializers(self):
+        for serializer in map(serializers.__dict__.get, serializers.__serializers__):
+            self.assertTrue(issubclass(serializer.klass, _Type),
+                f"_Serializer <{serializer.name}>: .klass field must be a subclass " \
+                    f"of _Type (got {serializer.klass}).")
+
+            self.assertListEqual(serializer.get_labels(), list(serializer.klass.__annotations__),
+                f"_Serializer <{serializer.name}> and _Type <{serializer.klass.__name__}> " \
+                    "must have matching labels and fields.")
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/bfxapi/tests/test_ws_capacity.py b/bfxapi/tests/test_ws_capacity.py
deleted file mode 100644
index 48cdbf96..00000000
--- a/bfxapi/tests/test_ws_capacity.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import pytest
-import json
-import time
-import asyncio
-from .helpers import (create_stubbed_client, ws_publish_connection_init, ws_publish_auth_accepted)
-
-@pytest.mark.asyncio
-async def test_ws_creates_new_socket():
-  client = create_stubbed_client()
-  client.ws.ws_capacity = 5
-  # publish connection created message
-  await ws_publish_connection_init(client.ws)
-  # create a bunch of websocket subscriptions
-  for symbol in ['tXRPBTC', 'tLTCUSD']:
-    await client.ws.subscribe('candles', symbol, timeframe='1m')
-    assert len(client.ws.sockets) == 1
-  assert client.ws.get_total_available_capcity() == 3
-  # subscribe to a few more to force the lib to create a new ws conenction
-  for symbol in ['tETHBTC', 'tBTCUSD', 'tETHUSD', 'tLTCBTC']:
-    await client.ws.subscribe('candles', symbol, timeframe='1m')
-  assert len(client.ws.sockets) == 2
-  assert client.ws.get_total_available_capcity() == 4
-
-@pytest.mark.asyncio
-async def test_ws_uses_authenticated_socket():
-  client = create_stubbed_client()
-  client.ws.ws_capacity = 2
-  # publish connection created message
-  await ws_publish_connection_init(client.ws)
-  # create a bunch of websocket subscriptions
-  for symbol in ['tXRPBTC', 'tLTCUSD', 'tETHBTC', 'tBTCUSD', 'tETHUSD', 'tLTCBTC']:
-    await client.ws.subscribe('candles', symbol, timeframe='1m')
-  # publish connection created message on socket (0 by default)
-  await ws_publish_connection_init(client.ws)
-  # send auth accepted (on socket by default)
-  await ws_publish_auth_accepted(client.ws)
-  # socket 0 should be the authenticated socket
-  assert client.ws.get_authenticated_socket().id == 0
-  # there should be no other authenticated sockets
-  for socket in client.ws.sockets.values():
-    if socket.id != 0:
-      assert socket.isAuthenticated == False
diff --git a/bfxapi/tests/test_ws_orderbook.py b/bfxapi/tests/test_ws_orderbook.py
deleted file mode 100644
index ebbee52a..00000000
--- a/bfxapi/tests/test_ws_orderbook.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import pytest
-from .helpers import create_stubbed_client, ws_publish_connection_init, ws_publish_conf_accepted
-
-@pytest.mark.asyncio
-async def test_checksum_generation():
-    client = create_stubbed_client()
-    symbol = "tXRPBTC"
-    # publish connection created message
-    await ws_publish_connection_init(client.ws)
-    # publish checksum flag accepted
-    await ws_publish_conf_accepted(client.ws, 131072)
-    # subscribe to order book
-    await client.ws.subscribe('book', symbol)
-    ## send subscription accepted
-    chanId = 123
-    await client.ws.publish({"event":"subscribed","channel":"book","chanId": chanId,"symbol": symbol,"prec":"P0","freq":"F0","len":"25","pair": symbol})
-    ## send orderbook snapshot
-    await client.ws.publish("""[123, [[0.0000886,1,1060.55466114],[0.00008859,1,1000],[0.00008858,1,2713.47159343],[0.00008857,1,4276.92870916],[0.00008856,2,6764.75562319],
-    [0.00008854,1,5641.48532401],[0.00008853,1,2255.92632223],[0.0000885,1,2256.69584601],[0.00008848,2,3630.3],[0.00008845,1,28195.70625766],
-    [0.00008844,1,15571.7],[0.00008843,1,2500],[0.00008841,1,64196.16117814],[0.00008838,1,7500],[0.00008837,2,2764.12999012],[0.00008834,2,10886.476298],
-    [0.00008831,1,20000],[0.0000883,1,1000],[0.00008829,2,2517.22175358],[0.00008828,1,450.45],[0.00008827,1,13000],[0.00008824,1,1500],[0.0000882,1,300],
-    [0.00008817,1,3000],[0.00008816,1,100],[0.00008864,1,-481.8549041],[0.0000887,2,-2141.77009092],[0.00008871,1,-2256.45433182],[0.00008872,1,-2707.58122743],
-    [0.00008874,1,-5640.31794092],[0.00008876,1,-29004.93294912],[0.00008878,1,-2500],[0.0000888,1,-20000],[0.00008881,2,-2880.15595827],[0.00008882,1,-27705.42933984],
-    [0.00008883,1,-4509.83708214],[0.00008884,1,-1500],[0.00008885,1,-2500],[0.00008888,1,-902.91405442],[0.00008889,1,-900],[0.00008891,1,-7500],
-    [0.00008894,1,-775.08564697],[0.00008896,1,-150],[0.00008899,3,-11628.02590049],[0.000089,2,-1299.7],[0.00008902,2,-4841.8],[0.00008904,3,-25320.46250083],
-    [0.00008909,1,-14000],[0.00008913,1,-123947.999],[0.00008915,2,-28019.6]]]""", is_json=False)
-    ## send some more price updates
-    await client.ws.publish("[{},[0.00008915,0,-1]]".format(chanId), is_json=False)
-    await client.ws.publish("[{},[0.00008837,1,56.54876269]]".format(chanId), is_json=False)
-    await client.ws.publish("[{},[0.00008873,1,-15699.9]]".format(chanId), is_json=False)
-    ## check checksum is the same as expected
-    expected_checksum = 30026640
-    actual_checksum = client.ws.orderBooks[symbol].checksum()
-    assert expected_checksum == actual_checksum
-
-@pytest.mark.asyncio
-async def test_checksum_really_samll_numbers_generation():
-    client = create_stubbed_client()
-    symbol = "tVETBTC"
-    # publish connection created message
-    await ws_publish_connection_init(client.ws)
-    # publish checksum flag accepted
-    await ws_publish_conf_accepted(client.ws, 131072)
-    # subscribe to order book
-    await client.ws.subscribe('book', symbol)
-    ## send subscription accepted
-    chanId = 123
-    await client.ws.publish({"event":"subscribed","channel":"book","chanId": chanId,"symbol": symbol,"prec":"P0","freq":"F0","len":"25","pair": symbol})
-    ## send orderbook snapshot
-    await client.ws.publish("""[123, [[0.00000121,5,249013.0209708],[0.0000012,6,518315.33310128],[0.00000119,4,566200.89],[0.00000118,2,260000],[0.00000117,1,100000],
-    [0.00000116,2,160000],[0.00000114,1,60000],[0.00000113,2,198500],[0.00000112,1,60000],[0.0000011,1,60000],[0.00000106,2,113868.87735849],[0.00000105,2,105000],
-    [0.00000103,1,3000],[0.00000102,2,105000],[0.00000101,2,202970],[0.000001,2,21000],[7e-7,1,10000],[6.6e-7,1,10000],[6e-7,1,100000],[4.9e-7,1,10000],[2.5e-7,1,2000],
-    [6e-8,1,100000],[5e-8,1,200000],[1e-8,4,640000],[0.00000122,7,-312043.19],[0.00000123,6,-415094.8939744],[0.00000124,5,-348181.23],[0.00000125,1,-12000],
-    [0.00000126,2,-143872.31],[0.00000127,1,-5000],[0.0000013,1,-5000],[0.00000134,1,-8249.18938656],[0.00000135,2,-230043.1337899],[0.00000136,1,-13161.25184766],
-    [0.00000145,1,-2914],[0.0000015,3,-54448.5],[0.00000152,2,-5538.54849594],[0.00000153,1,-62691.75475079],[0.00000159,1,-2914],[0.0000016,1,-52631.10296831],
-    [0.00000164,1,-4000],[0.00000166,1,-3831.46784605],[0.00000171,1,-14575.17730379],[0.00000174,1,-3124.81815395],[0.0000018,1,-18000],[0.00000182,1,-16000],
-    [0.00000186,1,-4000],[0.00000189,1,-10000.686624],[0.00000191,1,-14500]]]""", is_json=False)
-    ## send some more price updates
-    await client.ws.publish("[{},[0.00000121,4,228442.6609708]]".format(chanId), is_json=False)
-    await client.ws.publish("[{},[0.00000121,6,304023.8109708]]".format(chanId), is_json=False)
-    # await client.ws.publish("[{},[0.00008873,1,-15699.9]]".format(chanId), is_json=False)
-    ## check checksum is the same as expected
-    expected_checksum = 1770440002
-    actual_checksum = client.ws.orderBooks[symbol].checksum()
-    assert expected_checksum == actual_checksum
diff --git a/bfxapi/tests/test_ws_orders.py b/bfxapi/tests/test_ws_orders.py
deleted file mode 100644
index b28a8412..00000000
--- a/bfxapi/tests/test_ws_orders.py
+++ /dev/null
@@ -1,209 +0,0 @@
-import pytest
-import json
-import asyncio
-from .helpers import (create_stubbed_client, ws_publish_auth_accepted, ws_publish_connection_init,
-					  EventWatcher)
-
-@pytest.mark.asyncio
-async def test_submit_order():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-	## send new order
-	await client.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE MARKET')
-	last_sent = client.ws.get_last_sent_item()
-	sent_order_array = json.loads(last_sent['data'])
-	assert sent_order_array[1] == "on"
-	sent_order_json = sent_order_array[3]
-	assert sent_order_json['type'] == "EXCHANGE MARKET"
-	assert sent_order_json['symbol'] == "tBTCUSD"
-	assert sent_order_json['amount'] == "0.01"
-	assert sent_order_json['price'] == "19000"
-
-@pytest.mark.asyncio
-async def test_submit_update_order():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-	## send new order
-	await client.ws.update_order(123, price=100, amount=0.01, hidden=True)
-	last_sent = client.ws.get_last_sent_item()
-	sent_order_array = json.loads(last_sent['data'])
-	assert sent_order_array[1] == "ou"
-	sent_order_json = sent_order_array[3]
-	# {"id": 123, "price": "100", "amount": "0.01", "flags": 64}
-	assert sent_order_json['id'] == 123
-	assert sent_order_json['price'] == "100"
-	assert sent_order_json['amount'] == "0.01"
-	assert sent_order_json['flags'] == 64
-
-@pytest.mark.asyncio
-async def test_submit_cancel_order():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-	## send new order
-	await client.ws.cancel_order(123)
-	last_sent = client.ws.get_last_sent_item()
-	sent_order_array = json.loads(last_sent['data'])
-	assert sent_order_array[1] == "oc"
-	sent_order_json = sent_order_array[3]
-	assert sent_order_json['id'] == 123
-
-@pytest.mark.asyncio
-async def test_events_on_new_order():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-
-	## look for new order confirmation
-	o_new = EventWatcher.watch(client.ws, 'order_new')
-	await client.ws.publish([0,"on",[1151718504,None,1548262833910,"tBTCUSD",1548262833379,1548262833410,-1,-1,"EXCHANGE LIMIT",None,None,None,0,"ACTIVE",None,None,15980,0,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-	new_res = o_new.wait_until_complete()
-	assert new_res.amount_orig == -1
-	assert new_res.amount_filled == 0
-	assert new_res.price == 15980
-	assert new_res.type == 'EXCHANGE LIMIT'
-
-	## look for order update confirmation
-	o_update = EventWatcher.watch(client.ws, 'order_update')
-	await client.ws.publish([0,"ou",[1151718504,None,1548262833910,"tBTCUSD",1548262833379,1548262846964,-0.5,-1,"EXCHANGE LIMIT",None,None,None,0,"PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-	update_res = o_update.wait_until_complete()
-	assert update_res.amount_orig == -1
-	assert float(update_res.amount_filled) == -0.5
-	assert update_res.price == 15980
-	assert update_res.type == 'EXCHANGE LIMIT'
-
-	## look for closed notification
-	o_closed = EventWatcher.watch(client.ws, 'order_closed')
-	await client.ws.publish([0,"oc",[1151718504,None,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-	closed_res = o_closed.wait_until_complete()
-	assert new_res.amount_orig == -1
-	assert new_res.amount_filled == 0
-	assert new_res.price == 15980
-	assert new_res.type == 'EXCHANGE LIMIT'
-
-@pytest.mark.asyncio
-async def test_events_on_cancel_order():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-
-	## Create new order
-	await client.ws.publish([0,"on",[1151718565,None,1548325124885,"tBTCUSD",1548325123435,1548325123460,1,1,"EXCHANGE LIMIT",None,None,None,0,"ACTIVE",None,None,10,0,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-
-	## look for order closed confirmation
-	o_close = EventWatcher.watch(client.ws, 'order_closed')
-	await client.ws.publish([0,"oc",[1151718565,None,1548325124885,"tBTCUSD",1548325123435,1548325123548,1,1,"EXCHANGE LIMIT",None,None,None,0,"CANCELED",None,None,10,0,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-	close_res = o_close.wait_until_complete()
-	assert close_res.amount_orig == 1
-	assert float(close_res.amount_filled) == 0
-	assert close_res.price == 10
-	assert close_res.type == 'EXCHANGE LIMIT'
-
-@pytest.mark.asyncio
-async def test_closed_callback_on_submit_order_closed():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-	async def c(order):
-		client.ws._emit('c1', order)
-	callback_wait = EventWatcher.watch(client.ws, 'c1')
-	# override cid generation
-	client.ws.orderManager._gen_unique_cid = lambda: 123
-	await client.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE MARKET', onClose=c)
-	await client.ws.publish([0,"oc",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-	callback_wait.wait_until_complete()
-
-
-@pytest.mark.asyncio
-async def test_confirmed_callback_on_submit_order_closed():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-	async def c(order):
-		client.ws._emit('c1', order)
-	callback_wait = EventWatcher.watch(client.ws, 'c1')
-	# override cid generation
-	client.ws.orderManager._gen_unique_cid = lambda: 123
-	await client.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE MARKET', onConfirm=c)
-	await client.ws.publish([0,"oc",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-	callback_wait.wait_until_complete()
-
-@pytest.mark.asyncio
-async def test_confirmed_callback_on_submit_new_order():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-	async def c(order):
-		client.ws._emit('c1', order)
-	callback_wait = EventWatcher.watch(client.ws, 'c1')
-	# override cid generation
-	client.ws.orderManager._gen_unique_cid = lambda: 123
-	await client.ws.submit_order('tBTCUSD', 19000, 0.01, 'EXCHANGE MARKET', onConfirm=c)
-	await client.ws.publish([0,"on",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262833410,-1,-1,"EXCHANGE LIMIT",None,None,None,0,"ACTIVE",None,None,15980,0,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-	callback_wait.wait_until_complete()
-
-@pytest.mark.asyncio
-async def test_confirmed_callback_on_submit_order_update():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-	async def c(order):
-		client.ws._emit('c1', order)
-	callback_wait = EventWatcher.watch(client.ws, 'c1')
-	# override cid generation
-	client.ws.orderManager._gen_unique_cid = lambda: 123
-	await client.ws.update_order(123, price=100, onConfirm=c)
-	await client.ws.publish([0,"ou",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262846964,-0.5,-1,"EXCHANGE LIMIT",None,None,None,0,"PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-	callback_wait.wait_until_complete()
-
-@pytest.mark.asyncio
-async def test_confirmed_callback_on_submit_cancel_order():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-	async def c(order):
-		client.ws._emit('c1', order)
-	callback_wait = EventWatcher.watch(client.ws, 'c1')
-	# override cid generation
-	client.ws.orderManager._gen_unique_cid = lambda: 123
-	await client.ws.cancel_order(123, onConfirm=c)
-	await client.ws.publish([0,"oc",[123,None,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-	callback_wait.wait_until_complete()
-
-@pytest.mark.asyncio
-async def test_confirmed_callback_on_submit_cancel_group_order():
-	client = create_stubbed_client()
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	## send auth accepted
-	await ws_publish_auth_accepted(client.ws)
-	async def c(order):
-		client.ws._emit('c1', order)
-	callback_wait = EventWatcher.watch(client.ws, 'c1')
-	# override cid generation
-	client.ws.orderManager._gen_unique_cid = lambda: 123
-	await client.ws.cancel_order_group(123, onConfirm=c)
-	await client.ws.publish([0,"oc",[1548262833910,123,1548262833910,"tBTCUSD",1548262833379,1548262888016,0,-1,"EXCHANGE LIMIT",None,None,None,0,"EXECUTED @ 15980.0(-0.5): was PARTIALLY FILLED @ 15980.0(-0.5)",None,None,15980,15980,0,0,None,None,None,0,0,None,None,None,"API>BFX",None,None,None]])
-	callback_wait.wait_until_complete()
diff --git a/bfxapi/tests/test_ws_subscriptions.py b/bfxapi/tests/test_ws_subscriptions.py
deleted file mode 100644
index 7e1866ac..00000000
--- a/bfxapi/tests/test_ws_subscriptions.py
+++ /dev/null
@@ -1,140 +0,0 @@
-import pytest
-import json
-from .helpers import (create_stubbed_client, ws_publish_connection_init, EventWatcher)
-
-@pytest.mark.asyncio
-async def test_submit_subscribe():
-	client = create_stubbed_client()
-	symb = 'tXRPBTC'
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-
-	# Create new subscription to orderbook
-	await client.ws.subscribe('book', symb)
-	last_sent = client.ws.get_last_sent_item()
-	sent_sub = json.loads(last_sent['data'])
-	# {'time': 1548327054030, 'data': '{"event": "subscribe", "channel": "book", "symbol": "tXRPBTC"}'}
-	assert sent_sub['event'] == "subscribe"
-	assert sent_sub['channel'] == "book"
-	assert sent_sub['symbol'] == symb
-
-	# create new subscription to trades
-	await client.ws.subscribe('trades', symb)
-	last_sent = client.ws.get_last_sent_item()
-	sent_sub = json.loads(last_sent['data'])
-	# {'event': 'subscribe', 'channel': 'trades', 'symbol': 'tBTCUSD'}
-	assert sent_sub['event'] == 'subscribe'
-	assert sent_sub['channel'] == 'trades'
-	assert sent_sub['symbol'] == symb
-
-	# create new subscription to candles
-	await client.ws.subscribe('candles', symb, timeframe='1m')
-	last_sent = client.ws.get_last_sent_item()
-	sent_sub = json.loads(last_sent['data'])
-	#{'event': 'subscribe', 'channel': 'candles', 'symbol': 'tBTCUSD', 'key': 'trade:1m:tBTCUSD'}
-	assert sent_sub['event'] == 'subscribe'
-	assert sent_sub['channel'] == 'candles'
-	assert sent_sub['key'] == 'trade:1m:{}'.format(symb)
-
-@pytest.mark.asyncio
-async def test_event_subscribe():
-	client = create_stubbed_client()
-	symb = 'tXRPBTC'
-	pair = 'XRPBTC'
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	# create a new subscription
-	await client.ws.subscribe('trades', symb)
-	# announce subscription was successful
-	sub_watch = EventWatcher.watch(client.ws, 'subscribed')
-	await client.ws.publish({"event":"subscribed","channel":"trades","chanId":2,"symbol":symb,"pair":pair})
-	s_res = sub_watch.wait_until_complete()
-	assert s_res.channel_name == 'trades'
-	assert s_res.symbol == symb
-	assert s_res.is_subscribed_bool == True
-	assert s_res.chan_id == 2
-
-@pytest.mark.asyncio
-async def test_submit_unsubscribe():
-	client = create_stubbed_client()
-	symb = 'tXRPBTC'
-	pair = 'XRPBTC'
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	# create new subscription to trades
-	await client.ws.subscribe('trades', symb)
-	 # announce subscription was successful
-	sub_watch = EventWatcher.watch(client.ws, 'subscribed')
-	await client.ws.publish({"event":"subscribed","channel":"trades","chanId":2,"symbol":symb,"pair":pair})
-	s_res = sub_watch.wait_until_complete()
-	# unsubscribe from channel
-	await s_res.unsubscribe()
-	last_sent = client.ws.get_last_sent_item()
-	sent_unsub = json.loads(last_sent['data'])
-	# {'event': 'unsubscribe', 'chanId': 2}
-	assert sent_unsub['event'] == 'unsubscribe'
-	assert sent_unsub['chanId'] == 2
-
-@pytest.mark.asyncio
-async def test_event_unsubscribe():
-	client = create_stubbed_client()
-	symb = 'tXRPBTC'
-	pair = 'XRPBTC'
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	# create new subscription to trades
-	await client.ws.subscribe('trades', symb)
-	 # announce subscription was successful
-	sub_watch = EventWatcher.watch(client.ws, 'subscribed')
-	await client.ws.publish({"event":"subscribed","channel":"trades","chanId":2,"symbol":symb,"pair":pair})
-	s_res = sub_watch.wait_until_complete()
-	# unsubscribe from channel
-	await s_res.unsubscribe()
-	last_sent = client.ws.get_last_sent_item()
-	sent_unsub = json.loads(last_sent['data'])
-	
-	# publish confirmation of unsubscribe
-	unsub_watch = EventWatcher.watch(client.ws, 'unsubscribed')
-	await client.ws.publish({"event":"unsubscribed","status":"OK","chanId":2})
-	unsub_res = unsub_watch.wait_until_complete()
-	assert s_res.channel_name == 'trades'
-	assert s_res.symbol == symb
-	assert s_res.is_subscribed_bool == False
-	assert s_res.chan_id == 2
-
-@pytest.mark.asyncio
-async def test_submit_resubscribe():
-	client = create_stubbed_client()
-	symb = 'tXRPBTC'
-	pair = 'XRPBTC'
-	# publish connection created message
-	await ws_publish_connection_init(client.ws)
-	# request two new subscriptions
-	await client.ws.subscribe('book', symb)
-	await client.ws.subscribe('trades', symb)
-	# confirm subscriptions
-	await client.ws.publish({"event":"subscribed","channel":"trades","chanId":2,"symbol":symb,"pair":pair})
-	await client.ws.publish({"event":"subscribed","channel":"book","chanId":3,"symbol":symb,"prec":"P0","freq":"F0","len":"25","pair":pair})
-	# call resubscribe all
-	await client.ws.resubscribe_all()
-	## assert that 2 unsubscribe requests were sent
-	last_sent = client.ws.get_sent_items()[-2:]
-	for i in last_sent:
-		data = json.loads(i['data'])
-		assert data['event'] == 'unsubscribe'
-		assert (data['chanId'] == 2 or data['chanId'] == 3)
-	## confirm unsubscriptions
-	await client.ws.publish({"event":"unsubscribed","status":"OK","chanId":2})
-	await client.ws.publish({"event":"unsubscribed","status":"OK","chanId":3})
-
-	## confirm subscriptions
-	# await client.ws.publish({"event":"subscribed","channel":"trades","chanId":2,"symbol":symb,"pair":pair})
-	# await client.ws.publish({"event":"subscribed","channel":"book","chanId":3,"symbol":symb,"prec":"P0","freq":"F0","len":"25","pair":pair})
-	# wait for emit of event
-	n_last_sent = client.ws.get_sent_items()[-2:]
-	for i in n_last_sent:
-		data = json.loads(i['data'])
-		# print (data)
-		assert data['event'] == 'subscribe'
-		assert (data['channel'] == 'book' or data['channel'] == 'trades')
-		assert data['symbol'] == symb
diff --git a/bfxapi/utils/testing_tools.py b/bfxapi/utils/testing_tools.py
deleted file mode 100644
index 121ff514..00000000
--- a/bfxapi/utils/testing_tools.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import time
-import json
-import asyncio
-
-from .. import Client, BfxWebsocket
-
-def get_now():
-	  return int(round(time.time() * 1000))
-
-class StubbedWebsocket(BfxWebsocket):
-	def __new__(cls, *args, **kwargs):
-		instance = super(StubbedWebsocket, cls).__new__(cls, *args, **kwargs)
-		instance.sent_items = []
-		instance.published_items = []
-		return instance
-
-	async def _main(self, host):
-		print ("Faking wesocket connection to {}".format(host))
-
-	async def publish(self, data, is_json=True):
-		self.published_items += [{
-		'time': get_now(),
-		'data': data
-		}]
-		# convert to string and push through the websocket
-		data = json.dumps(data) if is_json else data
-		return await self.on_message(data)
-
-	async def publish_auth_confirmation(self):
-		return self.publish({"event":"auth","status":"OK","chanId":0,"userId":269499,"auth_id":"58aa0472-b1a9-4690-8ab8-300d68e66aaf","caps":{"orders":{"read":1,"write":1},"account":{"read":1,"write":0},"funding":{"read":1,"write":1},"history":{"read":1,"write":0},"wallets":{"read":1,"write":1},"withdraw":{"read":0,"write":1},"positions":{"read":1,"write":1}}})
-
-	async def send(self, data_string):
-		self.sent_items += [{
-		'time': get_now(),
-		'data': data_string
-		}]
-
-	def get_published_items(self):
-		return self.published_items
-
-	def get_sent_items(self):
-		return self.sent_items
-
-	def get_last_sent_item(self):
-		return self.sent_items[-1:][0]
-
-	def get_sent_items_count(self):
-		return len(self.sent_items)
-
-class EventWatcher():
-
-	def __init__(self, ws, event):
-		self.value = None
-		self.event = event
-		ws.once(event, self._finish)
-
-	def _finish(self, value):
-		self.value = value or {}
-
-	@classmethod
-	def watch(cls, ws, event):
-		return EventWatcher(ws, event)
-
-	def wait_until_complete(self, max_wait_time=5):
-		counter = 0
-		while self.value == None:
-			if counter > 5:
-				raise Exception('Wait time limit exceeded for event {}'.format(self.event))
-			time.sleep(1)
-			counter += 1
-		return self.value
-
-def create_stubbed_client(*args, **kwargs):
-	client = Client(*args, **kwargs)
-	# no support for rest stubbing yet
-	client.rest = None
-	client.ws = StubbedWebsocket(*args, **kwargs)
-	return client
-
-async def ws_publish_auth_accepted(ws):
-	  return await ws.publish({"event":"auth","status":"OK","chanId":0,"userId":269499,"auth_id":"58aa0472-b1a9-4690-8ab8-300d68e66aaf","caps":{"orders":{"read":1,"write":1},"account":{"read":1,"write":0},"funding":{"read":1,"write":1},"history":{"read":1,"write":0},"wallets":{"read":1,"write":1},"withdraw":{"read":0,"write":1},"positions":{"read":1,"write":1}}})
-
-async def ws_publish_connection_init(ws):
-	  return await ws.publish({"event":"info","version":2,"serverId":"748c00f2-250b-46bb-8519-ce1d7d68e4f0","platform":{"status":1}})
-
-async def ws_publish_conf_accepted(ws, flags_code):
-	  return await ws.publish({"event":"conf","status":"OK","flags":flags_code})