Repository: deriv-com/python-deriv-api (language: python)

Issue:
GH_action/public_repo_tests_migration_b2
https://app.clickup.com/t/20696747/CICD-416


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..536d276
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,26 @@
+version: 2
+jobs:
+  build:
+    docker:
+      - image: circleci/python:3.9.6
+        environment: # environment variables for primary container
+          PIPENV_VENV_IN_PROJECT: true
+    steps: # steps that comprise the `build` job
+      - checkout # check out source code to working directory
+      - run: sudo chown -R circleci:circleci /usr/local/bin
+      - restore_cache:
+      # Read about caching dependencies: https://circleci.com/docs/2.0/caching/
+          key: deps9-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
+      - run:
+          command: |
+            make setup
+      - save_cache: # cache Python dependencies using checksum of Pipfile as the cache-key
+          key: deps9-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
+          paths:
+            - "venv"
+      - run:
+          command: |
+            make test
+      - run:
+          command: |
+            make coverage
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8d198ff
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+__pycache_/
+*.py[cod]
+.pytest_cache/
+.eggs/
+venv/
+*.egg-info/
+.idea
+dist
+.coverage
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7dbcfcb
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Deriv Group Services Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..190f5c8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+.PHONY: all setup test doc gh-pages build
+all: setup test
+setup:
+	pip3 install pipenv && pipenv install --dev
+test:
+	pipenv run python setup.py pytest
+doc:
+	pdoc deriv_api --force --html -o docs/html --template-dir docs/templates
+gh-pages:
+	pdoc deriv_api --force --html -o /tmp/python-deriv-api-docs --template-dir docs/templates && git add . && git stash && git checkout gh-pages && cp -r /tmp/python-deriv-api-docs/deriv_api/* . && git add . && git commit -m 'Update docs' && git push && git checkout -
+build:
+	pip3 install build && python3 -m build
+coverage:
+	pipenv run coverage run --source deriv_api -m pytest && pipenv run coverage report -m
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000..fc103fc
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,15 @@
+[packages]
+websockets = "==9.1"
+rx = "==3.1.1"
+deriv-api = {editable = true, path = "."}
+
+[dev-packages]
+pytest = "*"
+pytest-runner = "*"
+pytest-mock = "*"
+pytest-asyncio = "*"
+pdoc3 = "*"
+coverage = "===4.5.4"
+
+[requires]
+python_version = "3.9.6"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000..f878966
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,316 @@
+{
+    "_meta": {
+        "hash": {
+            "sha256": "446c00cd6938a997e3461c67ab932960b169d40f2b92e67a46d653bdfc2aa9e9"
+        },
+        "pipfile-spec": 6,
+        "requires": {
+            "python_version": "3.9.6"
+        },
+        "sources": [
+            {
+                "name": "pypi",
+                "url": "https://pypi.org/simple",
+                "verify_ssl": true
+            }
+        ]
+    },
+    "default": {
+        "deriv-api": {
+            "editable": true,
+            "path": "."
+        },
+        "deriv-api-deriv": {
+            "editable": true,
+            "path": "."
+        },
+        "rx": {
+            "hashes": [
+                "sha256:0e0f2715a3452e95dcb5d6ea28ffe5742e832592bbcc67a48f394ef8ba871e6f",
+                "sha256:562851cfdb27fd5a218443cdbd682029684144dbafeb5dce34f6a709511282de"
+            ],
+            "index": "pypi",
+            "version": "==3.1.1"
+        },
+        "websockets": {
+            "hashes": [
+                "sha256:0dd4eb8e0bbf365d6f652711ce21b8fd2b596f873d32aabb0fbb53ec604418cc",
+                "sha256:1d0971cc7251aeff955aa742ec541ee8aaea4bb2ebf0245748fbec62f744a37e",
+                "sha256:1d6b4fddb12ab9adf87b843cd4316c4bd602db8d5efd2fb83147f0458fe85135",
+                "sha256:230a3506df6b5f446fed2398e58dcaafdff12d67fe1397dff196411a9e820d02",
+                "sha256:276d2339ebf0df4f45df453923ebd2270b87900eda5dfd4a6b0cfa15f82111c3",
+                "sha256:2cf04601633a4ec176b9cc3d3e73789c037641001dbfaf7c411f89cd3e04fcaf",
+                "sha256:3ddff38894c7857c476feb3538dd847514379d6dc844961dc99f04b0384b1b1b",
+                "sha256:48c222feb3ced18f3dc61168ca18952a22fb88e5eb8902d2bf1b50faefdc34a2",
+                "sha256:51d04df04ed9d08077d10ccbe21e6805791b78eac49d16d30a1f1fe2e44ba0af",
+                "sha256:597c28f3aa7a09e8c070a86b03107094ee5cdafcc0d55f2f2eac92faac8dc67d",
+                "sha256:5c8f0d82ea2468282e08b0cf5307f3ad022290ed50c45d5cb7767957ca782880",
+                "sha256:7189e51955f9268b2bdd6cc537e0faa06f8fffda7fb386e5922c6391de51b077",
+                "sha256:7df3596838b2a0c07c6f6d67752c53859a54993d4f062689fdf547cb56d0f84f",
+                "sha256:826ccf85d4514609219725ba4a7abd569228c2c9f1968e8be05be366f68291ec",
+                "sha256:836d14eb53b500fd92bd5db2fc5894f7c72b634f9c2a28f546f75967503d8e25",
+                "sha256:85db8090ba94e22d964498a47fdd933b8875a1add6ebc514c7ac8703eb97bbf0",
+                "sha256:85e701a6c316b7067f1e8675c638036a796fe5116783a4c932e7eb8e305a3ffe",
+                "sha256:900589e19200be76dd7cbaa95e9771605b5ce3f62512d039fb3bc5da9014912a",
+                "sha256:9147868bb0cc01e6846606cd65cbf9c58598f187b96d14dd1ca17338b08793bb",
+                "sha256:9e7fdc775fe7403dbd8bc883ba59576a6232eac96dacb56512daacf7af5d618d",
+                "sha256:ab5ee15d3462198c794c49ccd31773d8a2b8c17d622aa184f669d2b98c2f0857",
+                "sha256:ad893d889bc700a5835e0a95a3e4f2c39e91577ab232a3dc03c262a0f8fc4b5c",
+                "sha256:b2e71c4670ebe1067fa8632f0d081e47254ee2d3d409de54168b43b0ba9147e0",
+                "sha256:b43b13e5622c5a53ab12f3272e6f42f1ce37cd5b6684b2676cb365403295cd40",
+                "sha256:b4ad84b156cf50529b8ac5cc1638c2cf8680490e3fccb6121316c8c02620a2e4",
+                "sha256:be5fd35e99970518547edc906efab29afd392319f020c3c58b0e1a158e16ed20",
+                "sha256:caa68c95bc1776d3521f81eeb4d5b9438be92514ec2a79fececda814099c8314",
+                "sha256:d144b350045c53c8ff09aa1cfa955012dd32f00c7e0862c199edcabb1a8b32da",
+                "sha256:d2c2d9b24d3c65b5a02cac12cbb4e4194e590314519ed49db2f67ef561c3cf58",
+                "sha256:e9e5fd6dbdf95d99bc03732ded1fc8ef22ebbc05999ac7e0c7bf57fe6e4e5ae2",
+                "sha256:ebf459a1c069f9866d8569439c06193c586e72c9330db1390af7c6a0a32c4afd",
+                "sha256:f31722f1c033c198aa4a39a01905951c00bd1c74f922e8afc1b1c62adbcdd56a",
+                "sha256:f68c352a68e5fdf1e97288d5cec9296664c590c25932a8476224124aaf90dbcd"
+            ],
+            "index": "pypi",
+            "version": "==9.1"
+        }
+    },
+    "develop": {
+        "attrs": {
+            "hashes": [
+                "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
+                "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==21.2.0"
+        },
+        "coverage": {
+            "hashes": [
+                "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6",
+                "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650",
+                "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5",
+                "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d",
+                "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351",
+                "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755",
+                "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef",
+                "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca",
+                "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca",
+                "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9",
+                "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc",
+                "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5",
+                "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f",
+                "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe",
+                "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888",
+                "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5",
+                "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce",
+                "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5",
+                "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e",
+                "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e",
+                "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9",
+                "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437",
+                "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1",
+                "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c",
+                "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24",
+                "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47",
+                "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2",
+                "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28",
+                "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c",
+                "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7",
+                "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
+                "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
+            ],
+            "index": "pypi",
+            "version": "===4.5.4"
+        },
+        "importlib-metadata": {
+            "hashes": [
+                "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100",
+                "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"
+            ],
+            "markers": "python_version < '3.10'",
+            "version": "==4.8.2"
+        },
+        "iniconfig": {
+            "hashes": [
+                "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
+                "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
+            ],
+            "version": "==1.1.1"
+        },
+        "mako": {
+            "hashes": [
+                "sha256:4e9e345a41924a954251b95b4b28e14a301145b544901332e658907a7464b6b2",
+                "sha256:afaf8e515d075b22fad7d7b8b30e4a1c90624ff2f3733a06ec125f5a5f043a57"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.1.6"
+        },
+        "markdown": {
+            "hashes": [
+                "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006",
+                "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==3.3.6"
+        },
+        "markupsafe": {
+            "hashes": [
+                "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
+                "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
+                "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
+                "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194",
+                "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
+                "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
+                "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724",
+                "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
+                "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646",
+                "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
+                "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6",
+                "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a",
+                "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6",
+                "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad",
+                "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
+                "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38",
+                "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac",
+                "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
+                "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6",
+                "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047",
+                "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
+                "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
+                "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b",
+                "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
+                "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
+                "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a",
+                "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
+                "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1",
+                "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9",
+                "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864",
+                "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
+                "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee",
+                "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f",
+                "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
+                "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
+                "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
+                "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
+                "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b",
+                "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
+                "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86",
+                "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6",
+                "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
+                "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
+                "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
+                "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28",
+                "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e",
+                "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
+                "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
+                "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f",
+                "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d",
+                "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
+                "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
+                "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145",
+                "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
+                "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c",
+                "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1",
+                "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a",
+                "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207",
+                "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
+                "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53",
+                "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd",
+                "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134",
+                "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85",
+                "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9",
+                "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
+                "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
+                "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
+                "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
+                "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==2.0.1"
+        },
+        "packaging": {
+            "hashes": [
+                "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
+                "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==21.3"
+        },
+        "pdoc3": {
+            "hashes": [
+                "sha256:5f22e7bcb969006738e1aa4219c75a32f34c2d62d46dc9d2fb2d3e0b0287e4b7"
+            ],
+            "index": "pypi",
+            "version": "==0.10.0"
+        },
+        "pluggy": {
+            "hashes": [
+                "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
+                "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==1.0.0"
+        },
+        "py": {
+            "hashes": [
+                "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719",
+                "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==1.11.0"
+        },
+        "pyparsing": {
+            "hashes": [
+                "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
+                "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==3.0.6"
+        },
+        "pytest": {
+            "hashes": [
+                "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
+                "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
+            ],
+            "index": "pypi",
+            "version": "==6.2.5"
+        },
+        "pytest-asyncio": {
+            "hashes": [
+                "sha256:5f2a21273c47b331ae6aa5b36087047b4899e40f03f18397c0e65fa5cca54e9b",
+                "sha256:7496c5977ce88c34379df64a66459fe395cd05543f0a2f837016e7144391fcfb"
+            ],
+            "index": "pypi",
+            "version": "==0.16.0"
+        },
+        "pytest-mock": {
+            "hashes": [
+                "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3",
+                "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"
+            ],
+            "index": "pypi",
+            "version": "==3.6.1"
+        },
+        "pytest-runner": {
+            "hashes": [
+                "sha256:0fce5b8dc68760f353979d99fdd6b3ad46330b6b1837e2077a89ebcf204aac91",
+                "sha256:85f93af814438ee322b4ea08fe3f5c2ad53b253577f3bd84b2ad451fee450ac5"
+            ],
+            "index": "pypi",
+            "version": "==5.3.1"
+        },
+        "toml": {
+            "hashes": [
+                "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
+                "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
+            ],
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==0.10.2"
+        },
+        "zipp": {
+            "hashes": [
+                "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832",
+                "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==3.6.0"
+        }
+    }
+}
diff --git a/README.md b/README.md
index f0700e5..e8931b8 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,117 @@
 # python-deriv-api
-Python bindings for deriv.com websocket API
+A python implementation of deriv api library.
+
+[![PyPI](https://img.shields.io/pypi/v/python_deriv_api.svg?style=flat-square)](https://pypi.org/project/python_deriv_api/)
+[![Python 3.9.6](https://img.shields.io/badge/python-3.9.6-blue.svg)](https://www.python.org/download/releases/3.9.6/)
+[![Test status](https://circleci.com/gh/binary-com/python-deriv-api.svg?style=shield&circle-token=8b7c7b39615ea83053044854105bf90975b18126)](https://app.circleci.com/pipelines/github/binary-com/python-deriv-api)
+
+Go through [api.deriv.com](https://api.deriv.com/) to know simple easy steps on how to register and get access.
+Use this all-in-one python library to set up and make your app running or you can extend it.
+
+### Requirement
+Python (3.9.6 or higher is recommended) and pip3
+
+Note: There is bug in 'websockets' package with python 3.9.7, hope that will be fixed in 3.9.8 as mentioned in
+https://github.com/aaugustin/websockets/issues/1051. Please exclude python 3.9.7.
+
+# Installation
+
+`python3 -m pip install python_deriv_api`
+
+# Usage
+This is basic deriv-api python library which helps to make websockets connection and
+deal the API calls (including subscription).
+
+Import the module
+
+```
+from deriv_api import DerivAPI
+```
+
+Access 
+
+```
+api = DerivAPI(endpoint='ws://...', app_id=1234);
+response = await api.ping({'ping': 1})
+print(response) 
+```
+
+## Creating a websockets connection and API instantiation
+You can either create an instance of websockets and pass it as connection
+    or
+pass the endpoint and app_id to the constructor to create the connection for you.
+
+If you pass the connection it's up to you to reconnect in case the connection drops (cause API doesn't know how to create the same connection).
+
+
+- Pass the arguments needed to create a connection:
+```
+   api = DerivAPI(endpoint='ws://...', app_id=1234);
+```
+
+- create and use a previously opened connection:
+```
+   connection = await websockets.connect('ws://...')
+   api = DerivAPI(connection=connection)
+```
+
+# Documentation
+
+#### API reference
+The complete API reference is hosted [here](https://binary-com.github.io/python-deriv-api/)
+
+Examples [here](https://github.com/binary-com/python-deriv-api/examples)
+
+# Development
+```
+git clone https://github.com/binary-com/python-deriv-api
+cd python-deriv-api
+```
+Setup environment
+```
+make setup
+```
+
+Setup environment and run test
+```
+make all
+```
+
+#### Run test
+
+```
+python setup.py pytest
+```
+
+or
+
+```
+pytest
+```
+
+or
+
+```
+make test
+```
+#### Generate documentations
+
+Generate html version of the docs and publish it to gh-pages
+
+```
+make gh-pages
+```
+
+#### Build the package
+```
+make build
+```
+#### Run examples
+
+set token and run example
+
+```
+export DERIV_TOKEN=xxxTokenxxx
+PYTHONPATH=. python3 examples/simple_bot1.py
+```
+
diff --git a/deriv_api/__init__.py b/deriv_api/__init__.py
new file mode 100644
index 0000000..9ced8e9
--- /dev/null
+++ b/deriv_api/__init__.py
@@ -0,0 +1,14 @@
+"""
+.. include:: ../README.md
+.. include:: ../docs/usage_examples.md
+"""
+
+__pdoc__ = {
+    'deriv_api.errors': False,
+    'deriv_api.utils': False,
+    'deriv_api.easy_future': False
+}
+
+from .deriv_api import DerivAPI
+from .errors import AddedTaskError, APIError, ConstructionError, ResponseError
+
diff --git a/deriv_api/cache.py b/deriv_api/cache.py
new file mode 100644
index 0000000..e260a8f
--- /dev/null
+++ b/deriv_api/cache.py
@@ -0,0 +1,125 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING, Union
+
+if TYPE_CHECKING:
+    from deriv_api import DerivAPI
+from deriv_api.deriv_api_calls import DerivAPICalls
+from deriv_api.errors import ConstructionError
+from deriv_api.utils import dict_to_cache_key
+from deriv_api.in_memory import InMemory
+
+__pdoc__ = {
+    'deriv_api.cache.Cache.get': False,
+    'deriv_api.cache.Cache.get_by_msg_type': False,
+    'deriv_api.cache.Cache.has': False,
+    'deriv_api.cache.Cache.send': False,
+    'deriv_api.cache.Cache.set': False
+}
+
+
+class Cache(DerivAPICalls):
+    """
+        Cache - A class for implementing in-memory and persistent cache
+
+        The real implementation of the underlying cache is delegated to the storage
+        object (See the params).
+
+        The storage object needs to implement the API.
+
+        Examples
+        --------
+        - Read the latest active symbols
+        >>> symbols = await api.active_symbols()
+
+        - Read the data from cache if available
+        >>> cached_symbols = await api.cache.active_symbols()
+
+        Parameters
+        ----------
+            api : deriv_api.DerivAPI
+                API instance to get data that is not cached
+            storage : Object
+                A storage instance to use for caching
+        """
+
+    def __init__(self, api: Union[DerivAPI, Cache], storage: Union[InMemory, Cache]) -> None:
+        if not api:
+            raise ConstructionError('Cache object needs an API to work')
+
+        super().__init__()
+        self.api = api
+        self.storage = storage
+
+    async def send(self, request: dict) -> dict:
+        """Check if there is a cache for the request. If so then return that value.
+        Otherwise send the request by the api
+
+        Parameters
+        ----------
+        request : dict
+            API request
+
+        Returns
+        -------
+            API Response
+        """
+        if await self.has(request):
+            return await self.get(request)
+
+        response = await self.api.send(request)
+        self.set(request, response)
+        return response
+
+    async def has(self, request: dict) -> bool:
+        """Redirected to the method defined by the storage
+
+        Parameters
+        ----------
+        request : dict
+            API request
+
+        Returns
+        -------
+            Returns true if the request exists
+        """
+        return self.storage.has(dict_to_cache_key(request))
+
+    async def get(self, request: dict) -> dict:
+        """Redirected to the method defined by the storage
+
+        Parameters
+        ----------
+        request : dict
+            API request
+
+        Returns
+        -------
+            API response stored in
+        """
+        return self.storage.get(dict_to_cache_key(request))
+
+    async def get_by_msg_type(self, msg_type: str) -> dict:
+        """Redirected to the method defined by the storage
+
+        Parameters
+        ----------
+        msg_type : str
+            Request msg_type
+
+        Returns
+        -------
+            Returns response stored in
+        """
+        return self.storage.get_by_msg_type(msg_type)
+
+    def set(self, request: dict, response: dict) -> None:
+        """Redirected to the method defined by the storage
+
+        Parameters
+        ----------
+        request : dict
+            API request
+        response : dict
+            API response
+        """
+        return self.storage.set(dict_to_cache_key(request), response)
diff --git a/deriv_api/deriv_api.py b/deriv_api/deriv_api.py
new file mode 100644
index 0000000..277d1c8
--- /dev/null
+++ b/deriv_api/deriv_api.py
@@ -0,0 +1,437 @@
+import asyncio
+import json
+import logging
+import re
+from asyncio import Future
+from typing import Dict, Optional, Union, Coroutine
+
+import websockets
+from rx import operators as op
+from rx.subject import Subject
+from rx import Observable
+from websockets.legacy.client import WebSocketClientProtocol
+from websockets.exceptions import ConnectionClosedOK, ConnectionClosed
+
+from deriv_api.cache import Cache
+from deriv_api.easy_future import EasyFuture
+from deriv_api.deriv_api_calls import DerivAPICalls
+from deriv_api.errors import APIError, ConstructionError, ResponseError, AddedTaskError
+from deriv_api.in_memory import InMemory
+from deriv_api.subscription_manager import SubscriptionManager
+from deriv_api.utils import is_valid_url
+
+# TODO NEXT subscribe is not calling deriv_api_calls. that's , args not verified. can we improve it ?
+# TODO list these features missed
+# middleware is missed
+# events is missed
+
+logging.basicConfig(
+    format="%(asctime)s %(message)s",
+    level=logging.ERROR
+)
+
+__pdoc__ = {
+    'deriv_api.deriv_api.DerivAPI.send_and_get_source': False,
+    'deriv_api.deriv_api.DerivAPI.api_connect': False,
+    'deriv_api.deriv_api.DerivAPI.get_url': False,
+    'deriv_api.deriv_api.DerivAPI.add_task': False,
+    'deriv_api.deriv_api.DerivAPI.delete_from_expect_response': False,
+    'deriv_api.deriv_api.DerivAPI.disconnect': False,
+    'deriv_api.deriv_api.DerivAPI.send': False,
+    'deriv_api.deriv_api.DerivAPI.wsconnection': False,
+    'deriv_api.deriv_api.DerivAPI.storage': False
+}
+
+
+class DerivAPI(DerivAPICalls):
+    """
+    The minimum functionality provided by DerivAPI, provides direct calls to the API.
+    `api.cache` is available if you want to use the cached data
+
+    Examples
+    --------
+    - Pass the arguments needed to create a connection:
+    >>> api = DerivAPI(endpoint='ws://...', app_id=1234)
+
+    - create and use a previously opened connection:
+    >>> connection = await websockets.connect('ws://...')
+    >>> api = DerivAPI(connection=connection)
+
+    Parameters
+    ----------
+        options : dict with following keys
+            connection : websockets.WebSocketClientProtocol
+                A ready to use connection
+            endpoint : String
+                API server to connect to
+            app_id : String
+                Application ID of the API user
+            lang : String
+                Language of the API communication
+            brand : String
+                Brand name
+            middleware : String
+                A middleware to call on certain API actions
+    Properties
+    ----------
+    storage : Cache
+        If specified, uses a more persistent cache (local storage, etc.)
+    """
+
+    storage: None
+
+    def __init__(self, **options: str) -> None:
+        endpoint = options.get('endpoint', 'frontend.binaryws.com')
+        lang = options.get('lang', 'EN')
+        brand = options.get('brand', '')
+        cache = options.get('cache', InMemory())
+        storage: any = options.get('storage')
+        self.wsconnection: Optional[WebSocketClientProtocol] = None
+        self.wsconnection_from_inside = True
+        self.shouldReconnect = False
+        if options.get('connection'):
+            self.wsconnection: Optional[WebSocketClientProtocol] = options.get('connection')
+            self.wsconnection_from_inside = False
+        else:
+            if not options.get('app_id'):
+                raise ConstructionError('An app_id is required to connect to the API')
+
+            connection_argument = {
+                'app_id': str(options.get('app_id')),
+                'endpoint_url': self.get_url(endpoint),
+                'lang': lang,
+                'brand': brand
+            }
+            self.__set_api_url(connection_argument)
+            self.shouldReconnect = True
+
+        self.storage: Optional[Cache] = None
+        if storage:
+            self.storage = Cache(self, storage)
+        # If we have the storage look that one up
+        self.cache = Cache(self.storage if self.storage else self, cache)
+
+        self.req_id = 0
+        self.pending_requests: Dict[str, Subject] = {}
+        # resolved: connected  rejected: disconnected  pending: not connected yet
+        self.connected = EasyFuture()
+        self.subscription_manager: SubscriptionManager = SubscriptionManager(self)
+        self.sanity_errors: Subject = Subject()
+        self.expect_response_types = {}
+        self.wait_data_task = EasyFuture().set_result(1)
+        self.add_task(self.api_connect(), 'api_connect')
+        self.add_task(self.__wait_data(), 'wait_data')
+
+    async def __wait_data(self):
+        await self.connected
+        while self.connected.is_resolved():
+            try:
+                data = await self.wsconnection.recv()
+            except ConnectionClosed as err:
+                if self.connected.is_resolved():
+                    self.connected = EasyFuture().reject(err)
+                    self.connected.exception()  # call it to hide the warning of 'exception never retrieved'
+                self.sanity_errors.on_next(err)
+                break
+            except Exception as err:
+                self.sanity_errors.on_next(err)
+                continue
+            response = json.loads(data)
+            # TODO NEXT add self.events stream
+
+            # TODO NEXT onopen onclose, can be set by await connection
+            req_id = response.get('req_id', None)
+            if not req_id or req_id not in self.pending_requests:
+                self.sanity_errors.on_next(APIError("Extra response"))
+                continue
+            expect_response: Future = self.expect_response_types.get(response['msg_type'])
+            if expect_response and not expect_response.done():
+                expect_response.set_result(response)
+            request = response['echo_req']
+
+            # When one of the child subscriptions of `proposal_open_contract` has an error in the response,
+            # it should be handled in the callback of consumer instead. Calling `error()` with parent subscription
+            # will mark the parent subscription as complete and all child subscriptions will be forgotten.
+
+            is_parent_subscription = request and request.get('proposal_open_contract') and not request.get(
+                'contract_id')
+            if response.get('error') and not is_parent_subscription:
+                self.pending_requests[req_id].on_error(ResponseError(response))
+                continue
+
+            # on_error will stop a subject object
+            if self.pending_requests[req_id].is_stopped and response.get('subscription'):
+                # Source is already marked as completed. In this case we should
+                # send a forget request with the subscription id and ignore the response received.
+                subs_id = response['subscription']['id']
+                self.add_task(self.forget(subs_id), 'forget subscription')
+                continue
+
+            self.pending_requests[req_id].on_next(response)
+
+    def __set_api_url(self, connection_argument: dict) -> None:
+        """
+        Construct the websocket request url
+
+        Parameters
+        ----------
+        connection_argument : dict
+
+        """
+        self.api_url = connection_argument.get('endpoint_url') + "/websockets/v3?app_id=" + connection_argument.get(
+            'app_id') + "&l=" + connection_argument.get('lang') + "&brand=" + connection_argument.get('brand')
+
+    def __get_api_url(self) -> str:
+        """
+            Returns the api request url
+
+        Returns
+        -------
+            websocket api request url
+        """
+        return self.api_url
+
+    def get_url(self, original_endpoint: str) -> Union[str, ConstructionError]:
+        """
+        Validate and return the url
+
+        Parameters
+        ----------
+        original_endpoint : str
+            endpoint argument passed to constructor
+
+        Returns
+        -------
+            Returns api url. If validation fails then throws constructionError
+
+        """
+        if not isinstance(original_endpoint, str):
+            raise ConstructionError(f"Endpoint must be a string, passed: {type(original_endpoint)}")
+
+        match = re.match(r'((?:\w*://)*)(.*)', original_endpoint).groups()
+        protocol = match[0] if match[0] == "ws://" else "wss://"
+        endpoint = match[1]
+
+        url = protocol + endpoint
+        if not is_valid_url(url):
+            raise ConstructionError(f'Invalid URL:{original_endpoint}')
+
+        return url
+
+    async def api_connect(self) -> websockets.WebSocketClientProtocol:
+        """
+            Make a websockets connection and returns WebSocketClientProtocol
+        Returns
+        -------
+            Returns websockets.WebSocketClientProtocol
+        """
+        if not self.wsconnection and self.shouldReconnect:
+            self.wsconnection = await websockets.connect(self.api_url)
+        if self.connected.is_pending():
+            self.connected.resolve(True)
+        else:
+            self.connected = EasyFuture().resolve(True)
+        return self.wsconnection
+
+    async def send(self, request: dict) -> dict:
+        """
+        Send the API call and returns response
+
+        Parameters
+        ----------
+        request : dict
+            API request
+
+        Returns
+        -------
+            API response
+        """
+        response_future = self.send_and_get_source(request).pipe(op.first(), op.to_future())
+
+        response = await response_future
+        self.cache.set(request, response)
+        if self.storage:
+            self.storage.set(request, response)
+        return response
+
+    def send_and_get_source(self, request: dict) -> Subject:
+        """
+        Send message and returns Subject
+
+        Parameters
+        ----------
+        request : dict
+            API request
+
+        Returns
+        -------
+            Returns the Subject
+        """
+        pending = Subject()
+        if 'req_id' not in request:
+            self.req_id += 1
+            request['req_id'] = self.req_id
+        self.pending_requests[request['req_id']] = pending
+
+        async def send_message():
+            try:
+                await self.connected
+                await self.wsconnection.send(json.dumps(request))
+            except Exception as err:
+                pending.on_error(err)
+
+        self.add_task(send_message(), 'send_message')
+        return pending
+
+    async def subscribe(self, request: dict) -> Observable:
+        """
+        Subscribe to a given request
+
+        Parameters
+        ----------
+            request : dict
+                Subscribe request
+
+        Example
+        -------
+        >>> proposal_subscription = api.subscribe({"proposal_open_contract": 1, "contract_id": 11111111, "subscribe": 1})
+
+        Returns
+        -------
+            Observable
+        """
+
+        return await self.subscription_manager.subscribe(request)
+
+    async def forget(self, subs_id: str) -> dict:
+        """
+        Forget / unsubscribe the specific subscription.
+
+        Parameters
+        ----------
+            subs_id : str
+                subscription id
+
+        Returns
+        -------
+            Returns dict
+        """
+
+        return await self.subscription_manager.forget(subs_id)
+
+    async def forget_all(self, *types) -> dict:
+        """
+        Forget / unsubscribe the subscriptions of given types.
+
+        Possible values are: 'ticks', 'candles', 'proposal', 'proposal_open_contract', 'balance', 'transaction'
+
+        Parameter
+        ---------
+            *types : Any number of non-keyword arguments
+        Example
+        -------
+            api.forget_all("ticks", "candles")
+
+        Returns
+        -------
+            Returns the dict
+        """
+
+        return await self.subscription_manager.forget_all(*types)
+
+    async def disconnect(self) -> None:
+        """
+        Disconnect the websockets connection
+
+        """
+        if not self.connected.is_resolved():
+            return
+        self.connected = EasyFuture().reject(ConnectionClosedOK(1000, 'Closed by disconnect'))
+        self.connected.exception()  # fetch exception to avoid the warning of 'exception never retrieved'
+        if self.wsconnection_from_inside:
+            # TODO NEXT reconnect feature
+            self.shouldReconnect = False
+            await self.wsconnection.close()
+
+    def expect_response(self, *msg_types):
+        """
+        Expect specific message types
+
+        Parameters
+        ----------
+            *msg_types : variable number of non-key string argument
+                Expect these types to be received by the API
+        Returns
+        -------
+             Resolves to a single response or an array        
+        """
+        for msg_type in msg_types:
+            if msg_type not in self.expect_response_types:
+                future: Future = asyncio.get_event_loop().create_future()
+
+                async def get_by_msg_type(a_msg_type):
+                    nonlocal future
+                    val = await self.cache.get_by_msg_type(a_msg_type)
+                    if not val and self.storage:
+                        val = self.storage.get_by_msg_type(a_msg_type)
+                    if val:
+                        future.set_result(val)
+
+                self.add_task(get_by_msg_type(msg_type), 'get_by_msg_type')
+                self.expect_response_types[msg_type] = future
+
+        # expect on a single response returns a single response, not a list
+        if len(msg_types) == 1:
+            return self.expect_response_types[msg_types[0]]
+
+        return asyncio.gather(map(lambda t: self.expect_response_types[t], msg_types))
+
+    def delete_from_expect_response(self, request: dict):
+        """
+        Delete the given request message type from expect_response_types
+
+        Parameters
+        ----------
+        request : dict
+
+        """
+        response_type = None
+        for k in self.expect_response_types.keys():
+            if k in request:
+                response_type = k
+                break
+
+        if response_type and self.expect_response_types[response_type] \
+                and self.expect_response_types[response_type].done():
+            del self.expect_response_types[response_type]
+
+    def add_task(self, coroutine: Coroutine, name: str) -> None:
+        """
+        Add coroutine object to execution
+
+        Parameters
+        ----------
+        coroutine : Coroutine
+            Coroutine object
+        name: str
+            name of the Coroutine
+
+        """
+        name = 'deriv_api:' + name
+
+        async def wrap_coro(coru: Coroutine, pname: str) -> None:
+            try:
+                await coru
+            except Exception as err:
+                self.sanity_errors.on_next(AddedTaskError(err, pname))
+
+        asyncio.create_task(wrap_coro(coroutine, name), name=name)
+
+    async def clear(self):
+        """
+        Disconnect and cancel all the tasks        
+        """
+        await self.disconnect()
+        for task in asyncio.all_tasks():
+            if re.match(r"^deriv_api:", task.get_name()):
+                task.cancel('deriv api ended')
diff --git a/deriv_api/deriv_api_calls.py b/deriv_api/deriv_api_calls.py
new file mode 100644
index 0000000..7d194da
--- /dev/null
+++ b/deriv_api/deriv_api_calls.py
@@ -0,0 +1,7461 @@
+# This file was automatically generated by scripts/regen-py.pl at 20211103-103215
+
+
+from numbers import Number
+
+# =======================
+# ----- API Methods -----
+# =======================
+
+
+class DerivAPICalls:
+
+    async def account_closure(self, args=None):
+        """
+        This call allows clients to close all their accounts (including virtual-money account). It is assumed that all their accounts (excluding virtual) have no balance left.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_closure : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                reason : str
+                    Reason for closing off accounts.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_closure': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'reason': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'account_closure',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def account_security(self, args=None):
+        """
+        This call manages two-factor account authentication
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_security : int
+                    Must be 1
+                otp : str
+                    [Optional] OTP (one-time passcode) generated by a 2FA application like Authy, Google Authenticator or Yubikey.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                totp_action : str
+                    [Optional] Action to be taken for managing TOTP (time-based one-time password, RFC6238). Generate will create a secret key which is then returned in the secret_key response field, you can then enable by using that code in a 2FA application.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_security': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'otp': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'totp_action': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'account_security',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def account_statistics(self, args=None):
+        """
+        Send request to get account statistics
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_statistics : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_statistics': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'account_statistics',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def active_symbols(self, args=None):
+        """
+        Retrieve a list of all currently active symbols (underlying markets upon which contracts are available for trading).
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                active_symbols : str
+                    If you use brief, only a subset of fields will be returned.
+                landing_company : str
+                    [Optional] If you specify this field, only symbols available for trading by that landing company will be returned. If you are logged in, only symbols available for trading by your landing company will be returned regardless of what you specify in this field.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                product_type : str
+                    [Optional] If you specify this field, only symbols that can be traded through that product type will be returned.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'active_symbols': {
+                'required': 1,
+                'type': 'string'
+            },
+            'landing_company': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'product_type': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'active_symbols',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def affiliate_account_add(self, args=None):
+        """
+        This call opens a new affiliate account. This call can be used as as sign up endpoint.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_opening_reason : str
+                    [Optional] Purpose and reason for requesting the account opening.
+                account_turnover : str
+                    [Optional] The anticipated account turnover.
+                address_city : str
+                    [Optional] Within 100 characters.
+                address_line_1 : str
+                    Within 70 characters, with no leading whitespaces and may contain letters/numbers and/or any of following characters '.,:;()@#/-
+                address_line_2 : str
+                    [Optional] Within 70 characters.
+                address_postcode : str
+                    [Optional] Within 20 characters and may not contain '+'.
+                address_state : str
+                    [Optional] Possible value receive from states_list call.
+                affiliate_account_add : int
+                    Must be 1
+                affiliate_plan : str
+                    The affiliate plan the account will subscribe to
+                affiliate_token : str
+                    [Optional] Affiliate token, within 32 characters.
+                citizen : Any
+                    [Optional] Country of legal citizenship, 2-letter country code.
+                client_type : str
+                    [Optional] Indicates whether this is for a client requesting an account with professional status.
+                currency : str
+                    [Optional] To set currency of the account. List of supported currencies can be acquired with payout_currencies call.
+                date_of_birth : str
+                    Date of birth format: yyyy-mm-dd.
+                first_name : str
+                    Within 2-50 characters, use only letters, spaces, hyphens, full-stops or apostrophes.
+                last_name : str
+                    Within 2-50 characters, use only letters, spaces, hyphens, full-stops or apostrophes.
+                non_pep_declaration : int
+                    [Optional] Indicates client's self-declaration of not being a PEP/RCA (Politically Exposed Person/Relatives and Close Associates).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                phone : Any
+                    [Optional] Starting with + followed by 9-35 digits, hyphens or space.
+                place_of_birth : str
+                    [Optional] Place of birth, 2-letter country code.
+                req_id : int
+                    [Optional] Used to map request to response.
+                residence : str
+                    2-letter country code, possible value receive from residence_list call.
+                salutation : str
+                    [Optional] Accept any value in enum list.
+                secret_answer : str
+                    [Optional] Answer to secret question, within 4-50 characters. Required for new account and existing client details will be used if client open another account.
+                secret_question : str
+                    [Optional] Accept any value in enum list. Required for new account and existing client details will be used if client open another account.
+                tax_identification_number : str
+                    [Optional] Tax identification number. Only applicable for real money account. Required for maltainvest landing company.
+                tax_residence : str
+                    [Optional] Residence for tax purpose. Comma separated iso country code if multiple jurisdictions. Only applicable for real money account. Required for maltainvest landing company.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_opening_reason': {
+                'type': 'string'
+            },
+            'account_turnover': {
+                'type': 'string'
+            },
+            'address_city': {
+                'type': 'string'
+            },
+            'address_line_1': {
+                'type': 'string'
+            },
+            'address_line_2': {
+                'type': 'string'
+            },
+            'address_postcode': {
+                'type': 'string'
+            },
+            'address_state': {
+                'type': 'string'
+            },
+            'affiliate_account_add': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'affiliate_plan': {
+                'type': 'string'
+            },
+            'affiliate_token': {
+                'type': 'string'
+            },
+            'citizen': {},
+            'client_type': {
+                'type': 'string'
+            },
+            'currency': {
+                'type': 'string'
+            },
+            'date_of_birth': {
+                'type': 'string'
+            },
+            'first_name': {
+                'type': 'string'
+            },
+            'last_name': {
+                'type': 'string'
+            },
+            'non_pep_declaration': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'phone': {},
+            'place_of_birth': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'residence': {
+                'type': 'string'
+            },
+            'salutation': {
+                'type': 'string'
+            },
+            'secret_answer': {
+                'type': 'string'
+            },
+            'secret_question': {
+                'type': 'string'
+            },
+            'tax_identification_number': {
+                'type': 'string'
+            },
+            'tax_residence': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'affiliate_account_add',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def api_token(self, args=None):
+        """
+        This call manages API tokens
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                api_token : int
+                    Must be 1
+                delete_token : str
+                    [Optional] The token to remove.
+                new_token : str
+                    [Optional] The name of the created token.
+                new_token_scopes : Any
+                    [Optional] List of permission scopes to provide with the token.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                valid_for_current_ip_only : int
+                    [Optional] If you set this parameter during token creation, then the token created will only work for the IP address that was used to create the token
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'api_token': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'delete_token': {
+                'type': 'string'
+            },
+            'new_token': {
+                'type': 'string'
+            },
+            'new_token_scopes': {},
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'valid_for_current_ip_only': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'api_token',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def app_delete(self, args=None):
+        """
+        The request for deleting an application.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                app_delete : int
+                    Application app_id
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'app_delete': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'app_delete',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def app_get(self, args=None):
+        """
+        To get the information of the OAuth application specified by 'app_id'
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                app_get : int
+                    Application app_id
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'app_get': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'app_get',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def app_list(self, args=None):
+        """
+        List all of the account's OAuth applications
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                app_list : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'app_list': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'app_list',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def app_markup_details(self, args=None):
+        """
+        Retrieve details of `app_markup` according to criteria specified.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                app_id : int
+                    [Optional] Specific application app_id to report on.
+                app_markup_details : int
+                    Must be 1
+                client_loginid : str
+                    [Optional] Specific client loginid to report on, like CR12345
+                date_from : str
+                    Start date (epoch or YYYY-MM-DD HH:MM:SS). Results are inclusive of this time.
+                date_to : str
+                    End date (epoch or YYYY-MM-DD HH::MM::SS). Results are inclusive of this time.
+                description : int
+                    [Optional] If set to 1, will return app_markup transaction details.
+                limit : Number
+                    [Optional] Apply upper limit to count of transactions received.
+                offset : Number
+                    [Optional] Number of transactions to skip.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                sort : str
+                    [Optional] Sort direction on transaction_time. Other fields sort order is ASC.
+                sort_fields : Any
+                    [Optional] One or more of the specified fields to sort on. Default sort field is by transaction_time.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'app_id': {
+                'type': 'integer'
+            },
+            'app_markup_details': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'client_loginid': {
+                'type': 'string'
+            },
+            'date_from': {
+                'required': 1,
+                'type': 'string'
+            },
+            'date_to': {
+                'required': 1,
+                'type': 'string'
+            },
+            'description': {
+                'type': 'integer'
+            },
+            'limit': {
+                'type': 'numeric'
+            },
+            'offset': {
+                'type': 'numeric'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'sort': {
+                'type': 'string'
+            },
+            'sort_fields': {}
+        }
+
+        all_args = {
+            'method': 'app_markup_details',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def app_register(self, args=None):
+        """
+        Register a new OAuth application
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                app_markup_percentage : Number
+                    [Optional] Markup to be added to contract prices (as a percentage of contract payout).
+                app_register : int
+                    Must be 1
+                appstore : str
+                    [Optional] Application's App Store URL (if applicable).
+                github : str
+                    [Optional] Application's GitHub page (for open-source projects).
+                googleplay : str
+                    [Optional] Application's Google Play URL (if applicable).
+                homepage : str
+                    [Optional] Application's homepage URL.
+                name : str
+                    Application name.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                redirect_uri : str
+                    [Optional] The URL to redirect to after a successful login. Required if charging markup percentage
+                req_id : int
+                    [Optional] Used to map request to response.
+                scopes : Any
+                    List of permission scopes to grant the application.
+                verification_uri : str
+                    [Optional] Used when verify_email called. If available, a URL containing the verification token will be sent to the client's email, otherwise only the token will be sent.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'app_markup_percentage': {
+                'type': 'numeric'
+            },
+            'app_register': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'appstore': {
+                'type': 'string'
+            },
+            'github': {
+                'type': 'string'
+            },
+            'googleplay': {
+                'type': 'string'
+            },
+            'homepage': {
+                'type': 'string'
+            },
+            'name': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'redirect_uri': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'scopes': {
+                'required': 1
+            },
+            'verification_uri': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'app_register',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def app_update(self, args=None):
+        """
+        Update a new OAuth application
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                app_markup_percentage : Number
+                    [Optional] Markup to be added to contract prices (as a percentage of contract payout).
+                app_update : int
+                    Application app_id.
+                appstore : str
+                    [Optional] Application's App Store URL (if applicable).
+                github : str
+                    [Optional] Application's GitHub page (for open-source projects).
+                googleplay : str
+                    [Optional] Application's Google Play URL (if applicable).
+                homepage : str
+                    [Optional] Application's homepage URL.
+                name : str
+                    Application name.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                redirect_uri : str
+                    [Optional] The URL to redirect to after a successful login. Required if charging markup percentage.
+                req_id : int
+                    [Optional] Used to map request to response.
+                scopes : Any
+                    Change scopes will revoke all user's grants and log them out.
+                verification_uri : str
+                    [Optional] Used when verify_email called. If available, a URL containing the verification token will send to the client's email, otherwise only the token will be sent.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'app_markup_percentage': {
+                'type': 'numeric'
+            },
+            'app_update': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'appstore': {
+                'type': 'string'
+            },
+            'github': {
+                'type': 'string'
+            },
+            'googleplay': {
+                'type': 'string'
+            },
+            'homepage': {
+                'type': 'string'
+            },
+            'name': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'redirect_uri': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'scopes': {
+                'required': 1
+            },
+            'verification_uri': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'app_update',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def asset_index(self, args=None):
+        """
+        Retrieve a list of all available underlyings and the corresponding contract types and duration boundaries. If the user is logged in, only the assets available for that user's landing company will be returned.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                asset_index : int
+                    Must be 1
+                landing_company : str
+                    [Optional] If specified, will return only the underlyings for the specified landing company.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'asset_index': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'landing_company': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'asset_index',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def authorize(self, args=None):
+        """
+        Authorize current WebSocket session to act on behalf of the owner of a given token. Must precede requests that need to access client account, for example purchasing and selling contracts or viewing portfolio.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                add_to_login_history : int
+                    [Optional] Send this when you use api tokens for authorization and want to track activity using login_history call.
+                authorize : str
+                    Authentication token. May be retrieved from https://www.binary.com/en/user/security/api_tokenws.html
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'add_to_login_history': {
+                'type': 'integer'
+            },
+            'authorize': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'authorize',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def balance(self, args=None):
+        """
+        Get user account balance
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account : str
+                    [Optional] If set to all, return the balances of all accounts one by one; if set to current, return the balance of current account; if set as an account id, return the balance of that account.
+                balance : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] If set to 1, will send updates whenever the balance changes.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account': {
+                'type': 'string'
+            },
+            'balance': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'balance',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def buy(self, args=None):
+        """
+        Buy a Contract
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                buy : str
+                    Either the ID received from a Price Proposal (proposal call), or 1 if contract buy parameters are passed in the parameters field.
+                parameters : Any
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                price : Number
+                    Maximum price at which to purchase the contract.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] 1 to stream.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'buy': {
+                'required': 1,
+                'type': 'string'
+            },
+            'parameters': {
+                'amount': {
+                    'type': 'numeric'
+                },
+                'app_markup_percentage': {
+                    'type': 'numeric'
+                },
+                'barrier': {
+                    'type': 'string'
+                },
+                'barrier2': {
+                    'type': 'string'
+                },
+                'basis': {
+                    'type': 'string'
+                },
+                'cancellation': {
+                    'type': 'string'
+                },
+                'contract_type': {
+                    'required': 1,
+                    'type': 'string'
+                },
+                'currency': {
+                    'required': 1,
+                    'type': 'string'
+                },
+                'date_expiry': {
+                    'type': 'integer'
+                },
+                'date_start': {
+                    'type': 'integer'
+                },
+                'duration': {
+                    'type': 'integer'
+                },
+                'duration_unit': {
+                    'type': 'string'
+                },
+                'limit_order': {
+                    'stop_loss': {
+                        'type': 'numeric'
+                    },
+                    'take_profit': {
+                        'type': 'numeric'
+                    }
+                },
+                'multiplier': {
+                    'type': 'numeric'
+                },
+                'product_type': {
+                    'type': 'string'
+                },
+                'selected_tick': {
+                    'type': 'integer'
+                },
+                'symbol': {
+                    'required': 1,
+                    'type': 'string'
+                },
+                'trading_period_start': {
+                    'type': 'integer'
+                }
+            },
+            'passthrough': {},
+            'price': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'buy',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def buy_contract_for_multiple_accounts(self, args=None):
+        """
+        Buy a Contract for multiple Accounts specified by the `tokens` parameter. Note, although this is an authorized call, the contract is not bought for the authorized account.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                buy_contract_for_multiple_accounts : str
+                    Either the ID received from a Price Proposal (proposal call), or 1 if contract buy parameters are passed in the parameters field.
+                parameters : Any
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                price : Number
+                    Maximum price at which to purchase the contract.
+                req_id : int
+                    [Optional] Used to map request to response.
+                tokens : Any
+                    List of API tokens identifying the accounts for which the contract is bought. Note: If the same token appears multiple times or if multiple tokens designate the same account, the contract is bought multiple times for this account.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'buy_contract_for_multiple_accounts': {
+                'required': 1,
+                'type': 'string'
+            },
+            'parameters': {
+                'amount': {
+                    'type': 'numeric'
+                },
+                'app_markup_percentage': {
+                    'type': 'numeric'
+                },
+                'barrier': {
+                    'type': 'string'
+                },
+                'barrier2': {
+                    'type': 'string'
+                },
+                'basis': {
+                    'type': 'string'
+                },
+                'contract_type': {
+                    'required': 1,
+                    'type': 'string'
+                },
+                'currency': {
+                    'required': 1,
+                    'type': 'string'
+                },
+                'date_expiry': {
+                    'type': 'integer'
+                },
+                'date_start': {
+                    'type': 'integer'
+                },
+                'duration': {
+                    'type': 'integer'
+                },
+                'duration_unit': {
+                    'type': 'string'
+                },
+                'multiplier': {
+                    'type': 'numeric'
+                },
+                'selected_tick': {
+                    'type': 'integer'
+                },
+                'symbol': {
+                    'required': 1,
+                    'type': 'string'
+                }
+            },
+            'passthrough': {},
+            'price': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'tokens': {
+                'required': 1
+            }
+        }
+
+        all_args = {
+            'method': 'buy_contract_for_multiple_accounts',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def cancel(self, args=None):
+        """
+        Cancel contract with contract id
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                cancel : int
+                    Value should be the contract_id which received from the portfolio call.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'cancel': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'cancel',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def cashier(self, args=None):
+        """
+        Request the cashier info for the specified type.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                address : str
+                    [Optional] Address for crypto withdrawal. Only applicable for api type.
+                amount : Number
+                    [Optional] Amount for crypto withdrawal. Only applicable for api type.
+                cashier : str
+                    Operation which needs to be requested from cashier
+                dry_run : int
+                    [Optional] If set to 1, only validation is performed. Only applicable for withdraw using crypto provider and api type.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                provider : str
+                    [Optional] Cashier provider. crypto will be default option for crypto currency accounts.
+                req_id : int
+                    [Optional] Used to map request to response.
+                type : str
+                    [Optional] Data need to be returned from cashier. api is supported only for crypto provider.
+                verification_code : str
+                    [Optional] Email verification code (received from a verify_email call, which must be done first)
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'address': {
+                'type': 'string'
+            },
+            'amount': {
+                'type': 'numeric'
+            },
+            'cashier': {
+                'required': 1,
+                'type': 'string'
+            },
+            'dry_run': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'provider': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'type': {
+                'type': 'string'
+            },
+            'verification_code': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'cashier',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def cashier_payments(self, args=None):
+        """
+        List pending transactions. This can only be used for crypto transactions.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                cashier_payments : Number
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                provider : str
+                    [Optional] Cashier provider. crypto will be default option for crypto currency accounts.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] If set to 1, will send updates whenever there is update to crypto payments.
+                transaction_type : str
+                    [Optional] Type of transactions to receive.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'cashier_payments': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'passthrough': {},
+            'provider': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            },
+            'transaction_type': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'cashier_payments',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def cashier_withdrawal_cancel(self, args=None):
+        """
+        Request for cancelling a withdrawal. This can only be used for crypto transactions.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                cashier_withdrawal_cancel : Number
+                    Must be 1
+                id : str
+                    The unique identifier for the transaction.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'cashier_withdrawal_cancel': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'cashier_withdrawal_cancel',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def change_password(self, args=None):
+        """
+        Change Password. Note: This call is only available when authenticated using an OAuth token.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                change_password : Number
+                    Must be 1
+                new_password : str
+                    New password (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address)
+                old_password : str
+                    Old password for validation (non-empty string, accepts any printable ASCII character)
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'change_password': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'new_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'old_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'change_password',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def contract_update(self, args=None):
+        """
+        Update a contract condition.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                contract_id : int
+                    Internal unique contract identifier.
+                contract_update : int
+                    Must be 1
+                limit_order : Any
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'contract_id': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'contract_update': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'limit_order': {
+                'stop_loss': {},
+                'take_profit': {}
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'contract_update',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def contract_update_history(self, args=None):
+        """
+        Request for contract update history.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                contract_id : int
+                    Internal unique contract identifier.
+                contract_update_history : int
+                    Must be 1
+                limit : Number
+                    [Optional] Maximum number of historical updates to receive.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'contract_id': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'contract_update_history': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'limit': {
+                'type': 'numeric'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'contract_update_history',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def contracts_for(self, args=None):
+        """
+        For a given symbol, get the list of currently available contracts, and the latest barrier and duration limits for each contract.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                contracts_for : str
+                    The short symbol name (obtained from active_symbols call).
+                currency : str
+                    [Optional] Currency of the contract's stake and payout (obtained from payout_currencies call).
+                landing_company : str
+                    [Optional] Indicates which landing company to get a list of contracts for. If you are logged in, your account's landing company will override this field.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                product_type : str
+                    [Optional] If you specify this field, only contracts tradable through that contract type will be returned.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'contracts_for': {
+                'required': 1,
+                'type': 'string'
+            },
+            'currency': {
+                'type': 'string'
+            },
+            'landing_company': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'product_type': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'contracts_for',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def copy_start(self, args=None):
+        """
+        Start copy trader bets
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                assets : Any
+                    [Optional] Used to set assets to be copied. E.x ["frxUSDJPY", "R_50"]
+                copy_start : str
+                    API tokens identifying the accounts of trader which will be used to copy trades
+                max_trade_stake : Number
+                    [Optional] Used to set maximum trade stake to be copied.
+                min_trade_stake : Number
+                    [Optional] Used to set minimal trade stake to be copied.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                trade_types : Any
+                    [Optional] Used to set trade types to be copied. E.x ["CALL", "PUT"]
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'assets': {},
+            'copy_start': {
+                'required': 1,
+                'type': 'string'
+            },
+            'max_trade_stake': {
+                'type': 'numeric'
+            },
+            'min_trade_stake': {
+                'type': 'numeric'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'trade_types': {}
+        }
+
+        all_args = {
+            'method': 'copy_start',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def copy_stop(self, args=None):
+        """
+        Stop copy trader bets
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                copy_stop : str
+                    API tokens identifying the accounts which needs not to be copied
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'copy_stop': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'copy_stop',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def copytrading_list(self, args=None):
+        """
+        Retrieves a list of active copiers and/or traders for Copy Trading
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                copytrading_list : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'copytrading_list': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'copytrading_list',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def copytrading_statistics(self, args=None):
+        """
+        Retrieve performance, trading, risk and copiers statistics of trader.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                copytrading_statistics : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                trader_id : str
+                    The ID of the target trader.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'copytrading_statistics': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'trader_id': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'copytrading_statistics',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def document_upload(self, args=None):
+        """
+        Request KYC information from client
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                document_format : str
+                    Document file format
+                document_id : str
+                    [Optional] Document ID (required for Passport, Proof of ID and Driver's License)
+                document_issuing_country : str
+                    [Optional] 2-letter country code
+                document_type : str
+                    Document type
+                document_upload : int
+                    Must be 1
+                expected_checksum : str
+                    The checksum of the file to be uploaded
+                expiration_date : str
+                    [Optional] Document expiration date (required for Passport, Proof of ID and Driver's License)
+                file_size : int
+                    Document size (should be less than 10MB)
+                lifetime_valid : int
+                    [Optional] Boolean value that indicates whether this document is lifetime valid (only applies to POI document types, cancels out the expiration_date given if any)
+                page_type : str
+                    [Optional] To determine document side
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'document_format': {
+                'required': 1,
+                'type': 'string'
+            },
+            'document_id': {
+                'type': 'string'
+            },
+            'document_issuing_country': {
+                'type': 'string'
+            },
+            'document_type': {
+                'required': 1,
+                'type': 'string'
+            },
+            'document_upload': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'expected_checksum': {
+                'required': 1,
+                'type': 'string'
+            },
+            'expiration_date': {
+                'type': 'string'
+            },
+            'file_size': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'lifetime_valid': {
+                'type': 'integer'
+            },
+            'page_type': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'document_upload',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def economic_calendar(self, args=None):
+        """
+        Specify a currency to receive a list of events related to that specific currency. For example, specifying USD will return a list of USD-related events. If the currency is omitted, you will receive a list for all currencies.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                currency : str
+                    [Optional] Currency symbol.
+                economic_calendar : int
+                    Must be 1
+                end_date : int
+                    [Optional] End date.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                start_date : int
+                    [Optional] Start date.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'currency': {
+                'type': 'string'
+            },
+            'economic_calendar': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'end_date': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'start_date': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'economic_calendar',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def exchange_rates(self, args=None):
+        """
+        Retrieves the exchange rates from a base currency to all currencies supported by the system.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                base_currency : str
+                    Base currency (can be obtained from payout_currencies call)
+                exchange_rates : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'base_currency': {
+                'required': 1,
+                'type': 'string'
+            },
+            'exchange_rates': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'exchange_rates',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def forget(self, args=None):
+        """
+        Immediately cancel the real-time stream of messages with a specific ID.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                forget : str
+                    ID of the real-time stream of messages to cancel.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'forget': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'forget',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def forget_all(self, args=None):
+        """
+        Immediately cancel the real-time streams of messages of given type.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                forget_all : Any
+                    Cancel all streams by type. The value can be either a single type e.g. "ticks", or an array of multiple types e.g. ["candles", "ticks"].
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'forget_all': {
+                'required': 1
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'forget_all',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def get_account_status(self, args=None):
+        """
+        Get Account Status
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                get_account_status : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'get_account_status': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'get_account_status',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def get_financial_assessment(self, args=None):
+        """
+        This call gets the financial assessment details. The 'financial assessment' is a questionnaire that clients of certain Landing Companies need to complete, due to regulatory and KYC (know your client) requirements.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                get_financial_assessment : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'get_financial_assessment': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'get_financial_assessment',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def get_limits(self, args=None):
+        """
+        Trading and Withdrawal Limits for a given user
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                get_limits : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'get_limits': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'get_limits',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def get_self_exclusion(self, args=None):
+        """
+        Allows users to exclude themselves from the website for certain periods of time, or to set limits on their trading activities. This facility is a regulatory requirement for certain Landing Companies.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                get_self_exclusion : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'get_self_exclusion': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'get_self_exclusion',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def get_settings(self, args=None):
+        """
+        Get User Settings (email, date of birth, address etc)
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                get_settings : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'get_settings': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'get_settings',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def identity_verification_document_add(self, args=None):
+        """
+        Adds document information such as issuing country, id and type for identity verification processes.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                document_number : str
+                    The identification number of the document.
+                document_type : str
+                    The type of the document based on provided issuing_country (can obtained from residence_list call).
+                identity_verification_document_add : int
+                    Must be 1
+                issuing_country : str
+                    2-letter country code (can obtained from residence_list call).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'document_number': {
+                'required': 1,
+                'type': 'string'
+            },
+            'document_type': {
+                'required': 1,
+                'type': 'string'
+            },
+            'identity_verification_document_add': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'issuing_country': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'identity_verification_document_add',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def landing_company(self, args=None):
+        """
+        The company has a number of licensed subsidiaries in various jurisdictions, which are called Landing Companies. This call will return the appropriate Landing Company for clients of a given country. The landing company may differ for Gaming contracts (Synthetic Indices) and Financial contracts (Forex, Stock Indices, Commodities).
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                landing_company : str
+                    Client's 2-letter country code (obtained from residence_list call).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'landing_company': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'landing_company',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def landing_company_details(self, args=None):
+        """
+        The company has a number of licensed subsidiaries in various jurisdictions, which are called Landing Companies (and which are wholly owned subsidiaries of the Deriv Group). This call provides information about each Landing Company.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                landing_company_details : str
+                    Landing company shortcode.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'landing_company_details': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'landing_company_details',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def link_wallet(self, args=None):
+        """
+        Link a wallet to a trading app.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                client_id : str
+                    The unique identifier for this trading account.
+                link_wallet : Number
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                wallet_id : str
+                    The unique identifier for this wallet.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'client_id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'link_wallet': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'wallet_id': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'link_wallet',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def login_history(self, args=None):
+        """
+        Retrieve a summary of login history for user.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                limit : int
+                    [Optional] Apply limit to count of login history records.
+                login_history : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'limit': {
+                'type': 'integer'
+            },
+            'login_history': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'login_history',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def logout(self, args=None):
+        """
+        Logout the session
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                logout : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'logout': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'logout',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def mt5_deposit(self, args=None):
+        """
+        This call allows deposit into MT5 account from Binary account.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                amount : Number
+                    Amount to deposit (in the currency of from_binary); min = $1 or an equivalent amount, max = $20000 or an equivalent amount
+                from_binary : str
+                    Binary account loginid to transfer money from
+                mt5_deposit : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                to_mt5 : str
+                    MT5 account login to deposit money to
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'amount': {
+                'type': 'numeric'
+            },
+            'from_binary': {
+                'type': 'string'
+            },
+            'mt5_deposit': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'to_mt5': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'mt5_deposit',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def mt5_get_settings(self, args=None):
+        """
+        Get MT5 user account settings
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                login : str
+                    MT5 user login
+                mt5_get_settings : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'login': {
+                'required': 1,
+                'type': 'string'
+            },
+            'mt5_get_settings': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'mt5_get_settings',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def mt5_login_list(self, args=None):
+        """
+        Get list of MT5 accounts for client
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                mt5_login_list : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'mt5_login_list': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'mt5_login_list',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def mt5_new_account(self, args=None):
+        """
+        This call creates new MT5 user, either demo or real money user.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_type : str
+                    Account type. If set to 'financial', setting 'mt5_account_type' is also required.
+                address : str
+                    [Optional] The address of the user. The maximum length of this address field is 128 characters.
+                city : str
+                    [Optional] User's city of residence.
+                company : str
+                    [Optional] Name of the client's company. The maximum length of the company name is 64 characters.
+                country : str
+                    [Optional] 2-letter country code (value received from residence_list call).
+                currency : str
+                    [Optional] MT5 account currency, the default value will be the qualified account currency.
+                dry_run : int
+                    [Optional] If set to 1, only validation is performed.
+                email : str
+                    Email address
+                investPassword : str
+                    [Optional] The investor password of the account. For validation (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address).
+                leverage : Number
+                    Client leverage (from 1 to 1000).
+                mainPassword : str
+                    The master password of the account. For validation (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address). This field is required.
+                mt5_account_category : str
+                    [Optional] To choose whether account is conventional or swap_free. Unavailable for financial_stp MT5_account_type
+                mt5_account_type : str
+                    [Optional] Financial: Variable spreads, High leverage. Financial STP: Variable spreads, Medium Leverage, more products. If 'account_type' set to 'financial', setting 'mt5_account_type' is also required.
+                mt5_new_account : int
+                    Must be 1
+                name : str
+                    Client's name. The maximum length here is 101 characters.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                phone : str
+                    [Optional] User's phone number.
+                phonePassword : str
+                    [Optional] The user's phone password.
+                req_id : int
+                    [Optional] Used to map request to response.
+                server : Any
+                    [Optional] Trade server.
+                state : str
+                    [Optional] User's state (region) of residence.
+                zipCode : str
+                    [Optional] User's zip code.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_type': {
+                'required': 1,
+                'type': 'string'
+            },
+            'address': {
+                'type': 'string'
+            },
+            'city': {
+                'type': 'string'
+            },
+            'company': {
+                'type': 'string'
+            },
+            'country': {
+                'type': 'string'
+            },
+            'currency': {
+                'type': 'string'
+            },
+            'dry_run': {
+                'type': 'integer'
+            },
+            'email': {
+                'required': 1,
+                'type': 'string'
+            },
+            'investPassword': {
+                'type': 'string'
+            },
+            'leverage': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'mainPassword': {
+                'required': 1,
+                'type': 'string'
+            },
+            'mt5_account_category': {
+                'type': 'string'
+            },
+            'mt5_account_type': {
+                'type': 'string'
+            },
+            'mt5_new_account': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'name': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'phone': {
+                'type': 'string'
+            },
+            'phonePassword': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'server': {},
+            'state': {
+                'type': 'string'
+            },
+            'zipCode': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'mt5_new_account',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def mt5_password_change(self, args=None):
+        """
+        To change passwords of the MT5 account.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                login : str
+                    MT5 user login
+                mt5_password_change : int
+                    Must be 1
+                new_password : str
+                    New password of the account. For validation (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address).
+                old_password : str
+                    Old password for validation (non-empty string, accepts any printable ASCII character)
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                password_type : str
+                    [Optional] Type of the password to change.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'login': {
+                'required': 1,
+                'type': 'string'
+            },
+            'mt5_password_change': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'new_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'old_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'password_type': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'mt5_password_change',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def mt5_password_check(self, args=None):
+        """
+        This call validates the main password for the MT5 user
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                login : str
+                    MT5 user login
+                mt5_password_check : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                password : str
+                    The password of the account.
+                password_type : str
+                    [Optional] Type of the password to check.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'login': {
+                'required': 1,
+                'type': 'string'
+            },
+            'mt5_password_check': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'password_type': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'mt5_password_check',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def mt5_password_reset(self, args=None):
+        """
+        To reset the password of MT5 account.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                login : str
+                    MT5 user login
+                mt5_password_reset : int
+                    Must be 1
+                new_password : str
+                    New password of the account. For validation (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                password_type : str
+                    [Optional] Type of the password to reset.
+                req_id : int
+                    [Optional] Used to map request to response.
+                verification_code : str
+                    Email verification code (received from a verify_email call, which must be done first)
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'login': {
+                'required': 1,
+                'type': 'string'
+            },
+            'mt5_password_reset': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'new_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'password_type': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'verification_code': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'mt5_password_reset',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def mt5_withdrawal(self, args=None):
+        """
+        This call allows withdrawal from MT5 account to Binary account.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                amount : Number
+                    Amount to withdraw (in the currency of the MT5 account); min = $1 or an equivalent amount, max = $20000 or an equivalent amount.
+                from_mt5 : str
+                    MT5 account login to withdraw money from
+                mt5_withdrawal : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                to_binary : str
+                    Binary account loginid to transfer money to
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'amount': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'from_mt5': {
+                'required': 1,
+                'type': 'string'
+            },
+            'mt5_withdrawal': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'to_binary': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'mt5_withdrawal',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def new_account_maltainvest(self, args=None):
+        """
+        This call opens a new real-money account with the `maltainvest` Landing Company. This call can be made from a virtual-money account or real-money account at Deriv (Europe) Limited. If it is the latter, client information fields in this call will be ignored and data from your existing real-money account will be used.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                accept_risk : int
+                    Show whether client has accepted risk disclaimer.
+                account_opening_reason : str
+                    [Optional] Purpose and reason for requesting the account opening.
+                account_turnover : str
+                    [Optional] The anticipated account turnover.
+                address_city : str
+                    Within 100 characters
+                address_line_1 : str
+                    Within 70 characters, with no leading whitespaces and may contain letters/numbers and/or any of following characters '.,:;()@#/-
+                address_line_2 : str
+                    [Optional] Within 70 characters.
+                address_postcode : str
+                    [Optional] Within 20 characters and may not contain '+'.
+                address_state : str
+                    [Optional] Possible value receive from states_list call.
+                affiliate_token : str
+                    [Optional] Affiliate token, within 32 characters.
+                binary_options_trading_experience : str
+                    [Optional] Binary options trading experience.
+                binary_options_trading_frequency : str
+                    [Optional] Binary options trading frequency.
+                cfd_trading_experience : str
+                    [Optional] CFDs trading experience.
+                cfd_trading_frequency : str
+                    [Optional] CFDs trading frequency.
+                citizen : str
+                    [Optional] Country of legal citizenship, 2-letter country code. Possible value receive from residence_list call.
+                client_type : str
+                    [Optional] Indicates whether this is for a client requesting an account with professional status.
+                date_of_birth : str
+                    Date of birth format: yyyy-mm-dd.
+                education_level : str
+                    Level of Education
+                employment_industry : str
+                    Industry of Employment.
+                employment_status : str
+                    [Optional] Employment Status.
+                estimated_worth : str
+                    Estimated Net Worth.
+                first_name : str
+                    Within 2-50 characters, use only letters, spaces, hyphens, full-stops or apostrophes.
+                forex_trading_experience : str
+                    [Optional] Forex trading experience.
+                forex_trading_frequency : str
+                    [Optional] Forex trading frequency.
+                income_source : str
+                    Income Source.
+                last_name : str
+                    Within 2-50 characters, use only letters, spaces, hyphens, full-stops or apostrophes.
+                net_income : str
+                    Net Annual Income.
+                new_account_maltainvest : int
+                    Must be 1
+                non_pep_declaration : int
+                    [Optional] Indicates client's self-declaration of not being a PEP/RCA.
+                occupation : str
+                    Occupation.
+                other_instruments_trading_experience : str
+                    [Optional] Trading experience in other financial instruments.
+                other_instruments_trading_frequency : str
+                    [Optional] Trading frequency in other financial instruments.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                phone : Any
+                    [Optional] Starting with + followed by 9-35 digits, hyphens or space.
+                place_of_birth : str
+                    [Optional] Place of birth, 2-letter country code.
+                req_id : int
+                    [Optional] Used to map request to response.
+                residence : str
+                    2-letter country code, possible value receive from residence_list call.
+                salutation : str
+                    Accept any value in enum list.
+                secret_answer : str
+                    [Optional] Answer to secret question, within 4-50 characters.
+                secret_question : str
+                    [Optional] Accept any value in enum list.
+                source_of_wealth : str
+                    [Optional] Source of wealth.
+                tax_identification_number : str
+                    Tax identification number. Only applicable for real money account. Required for maltainvest landing company.
+                tax_residence : str
+                    Residence for tax purpose. Comma separated iso country code if multiple jurisdictions. Only applicable for real money account. Required for maltainvest landing company.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'accept_risk': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'account_opening_reason': {
+                'type': 'string'
+            },
+            'account_turnover': {
+                'type': 'string'
+            },
+            'address_city': {
+                'required': 1,
+                'type': 'string'
+            },
+            'address_line_1': {
+                'required': 1,
+                'type': 'string'
+            },
+            'address_line_2': {
+                'type': 'string'
+            },
+            'address_postcode': {
+                'type': 'string'
+            },
+            'address_state': {
+                'type': 'string'
+            },
+            'affiliate_token': {
+                'type': 'string'
+            },
+            'binary_options_trading_experience': {
+                'type': 'string'
+            },
+            'binary_options_trading_frequency': {
+                'type': 'string'
+            },
+            'cfd_trading_experience': {
+                'type': 'string'
+            },
+            'cfd_trading_frequency': {
+                'type': 'string'
+            },
+            'citizen': {
+                'type': 'string'
+            },
+            'client_type': {
+                'type': 'string'
+            },
+            'date_of_birth': {
+                'required': 1,
+                'type': 'string'
+            },
+            'education_level': {
+                'required': 1,
+                'type': 'string'
+            },
+            'employment_industry': {
+                'required': 1,
+                'type': 'string'
+            },
+            'employment_status': {
+                'type': 'string'
+            },
+            'estimated_worth': {
+                'required': 1,
+                'type': 'string'
+            },
+            'first_name': {
+                'required': 1,
+                'type': 'string'
+            },
+            'forex_trading_experience': {
+                'type': 'string'
+            },
+            'forex_trading_frequency': {
+                'type': 'string'
+            },
+            'income_source': {
+                'required': 1,
+                'type': 'string'
+            },
+            'last_name': {
+                'required': 1,
+                'type': 'string'
+            },
+            'net_income': {
+                'required': 1,
+                'type': 'string'
+            },
+            'new_account_maltainvest': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'non_pep_declaration': {
+                'type': 'integer'
+            },
+            'occupation': {
+                'required': 1,
+                'type': 'string'
+            },
+            'other_instruments_trading_experience': {
+                'type': 'string'
+            },
+            'other_instruments_trading_frequency': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'phone': {},
+            'place_of_birth': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'residence': {
+                'required': 1,
+                'type': 'string'
+            },
+            'salutation': {
+                'required': 1,
+                'type': 'string'
+            },
+            'secret_answer': {
+                'type': 'string'
+            },
+            'secret_question': {
+                'type': 'string'
+            },
+            'source_of_wealth': {
+                'type': 'string'
+            },
+            'tax_identification_number': {
+                'required': 1,
+                'type': 'string'
+            },
+            'tax_residence': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'new_account_maltainvest',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def new_account_real(self, args=None):
+        """
+        This call opens a new real-money account. This call can be made from a virtual-money or a real-money account. If it is the latter, client information fields in this call will be ignored and data from your existing real-money account will be used.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_opening_reason : str
+                    [Optional] Purpose and reason for requesting the account opening.
+                account_turnover : str
+                    [Optional] The anticipated account turnover.
+                address_city : str
+                    [Optional] Within 100 characters.
+                address_line_1 : str
+                    Within 70 characters, with no leading whitespaces and may contain letters/numbers and/or any of following characters '.,:;()@#/-
+                address_line_2 : str
+                    [Optional] Within 70 characters.
+                address_postcode : str
+                    [Optional] Within 20 characters and may not contain '+'.
+                address_state : str
+                    [Optional] Possible value receive from states_list call.
+                affiliate_token : str
+                    [Optional] Affiliate token, within 32 characters.
+                citizen : Any
+                    [Optional] Country of legal citizenship, 2-letter country code.
+                client_type : str
+                    [Optional] Indicates whether this is for a client requesting an account with professional status.
+                currency : str
+                    [Optional] To set currency of the account. List of supported currencies can be acquired with payout_currencies call.
+                date_of_birth : str
+                    Date of birth format: yyyy-mm-dd.
+                first_name : str
+                    Within 2-50 characters, use only letters, spaces, hyphens, full-stops or apostrophes.
+                last_name : str
+                    Within 2-50 characters, use only letters, spaces, hyphens, full-stops or apostrophes.
+                new_account_real : int
+                    Must be 1
+                non_pep_declaration : int
+                    [Optional] Indicates client's self-declaration of not being a PEP/RCA (Politically Exposed Person/Relatives and Close Associates).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                phone : Any
+                    [Optional] Starting with + followed by 9-35 digits, hyphens or space.
+                place_of_birth : str
+                    [Optional] Place of birth, 2-letter country code.
+                req_id : int
+                    [Optional] Used to map request to response.
+                residence : str
+                    2-letter country code, possible value receive from residence_list call.
+                salutation : str
+                    [Optional] Accept any value in enum list.
+                secret_answer : str
+                    [Optional] Answer to secret question, within 4-50 characters. Required for new account and existing client details will be used if client open another account.
+                secret_question : str
+                    [Optional] Accept any value in enum list. Required for new account and existing client details will be used if client open another account.
+                tax_identification_number : str
+                    [Optional] Tax identification number. Only applicable for real money account. Required for maltainvest landing company.
+                tax_residence : str
+                    [Optional] Residence for tax purpose. Comma separated iso country code if multiple jurisdictions. Only applicable for real money account. Required for maltainvest landing company.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_opening_reason': {
+                'type': 'string'
+            },
+            'account_turnover': {
+                'type': 'string'
+            },
+            'address_city': {
+                'type': 'string'
+            },
+            'address_line_1': {
+                'type': 'string'
+            },
+            'address_line_2': {
+                'type': 'string'
+            },
+            'address_postcode': {
+                'type': 'string'
+            },
+            'address_state': {
+                'type': 'string'
+            },
+            'affiliate_token': {
+                'type': 'string'
+            },
+            'citizen': {},
+            'client_type': {
+                'type': 'string'
+            },
+            'currency': {
+                'type': 'string'
+            },
+            'date_of_birth': {
+                'type': 'string'
+            },
+            'first_name': {
+                'type': 'string'
+            },
+            'last_name': {
+                'type': 'string'
+            },
+            'new_account_real': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'non_pep_declaration': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'phone': {},
+            'place_of_birth': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'residence': {
+                'type': 'string'
+            },
+            'salutation': {
+                'type': 'string'
+            },
+            'secret_answer': {
+                'type': 'string'
+            },
+            'secret_question': {
+                'type': 'string'
+            },
+            'tax_identification_number': {
+                'type': 'string'
+            },
+            'tax_residence': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'new_account_real',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def new_account_virtual(self, args=None):
+        """
+        Create a new virtual-money account.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                affiliate_token : str
+                    [Optional] Affiliate token, within 32 characters.
+                client_password : str
+                    Password (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address).
+                date_first_contact : str
+                    [Optional] Date of first contact, format: yyyy-mm-dd in GMT timezone.
+                email_consent : int
+                    [Optional] Boolean value: 1 or 0, indicating whether the client has given consent for marketing emails.
+                gclid_url : str
+                    [Optional] Google Click Identifier to track source.
+                new_account_virtual : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                residence : str
+                    2-letter country code (obtained from residence_list call).
+                signup_device : str
+                    [Optional] Show whether user has used mobile or desktop.
+                type : str
+                    Account type
+                utm_ad_id : Any
+                    [Optional] Identifier of particular ad. Value must match Regex pattern to be recorded
+                utm_adgroup_id : Any
+                    [Optional] Identifier of ad group in the campaign. Value must match Regex pattern to be recorded
+                utm_adrollclk_id : Any
+                    [Optional] Unique identifier of click on AdRoll ads platform. Value must match Regex pattern to be recorded
+                utm_campaign : Any
+                    [Optional] Identifies a specific product promotion or strategic campaign such as a spring sale or other promotions. Value must match Regex pattern to be recorded
+                utm_campaign_id : Any
+                    [Optional] Identifier of paid ad campaign. Value must match Regex pattern to be recorded
+                utm_content : Any
+                    [Optional] Used to differentiate similar content, or links within the same ad. Value must match Regex pattern to be recorded
+                utm_fbcl_id : Any
+                    [Optional] Unique identifier of click on Facebook ads platform. Value must match Regex pattern to be recorded
+                utm_gl_client_id : Any
+                    [Optional] Unique visitor identifier on Google Ads platform. Value must match Regex pattern to be recorded
+                utm_medium : Any
+                    [Optional] Identifies the medium the link was used upon such as: email, CPC, or other methods of sharing. Value must match Regex pattern to be recorded
+                utm_msclk_id : Any
+                    [Optional] Unique click identifier on Microsoft Bing ads platform. Value must match Regex pattern to be recorded
+                utm_source : Any
+                    [Optional] Identifies the source of traffic such as: search engine, newsletter, or other referral. Value must match Regex pattern to be recorded
+                utm_term : Any
+                    [Optional] Used to send information related to the campaign term like paid search keywords. Value must match Regex pattern to be recorded
+                verification_code : str
+                    Email verification code (received from a verify_email call, which must be done first).
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'affiliate_token': {
+                'type': 'string'
+            },
+            'client_password': {
+                'type': 'string'
+            },
+            'date_first_contact': {
+                'type': 'string'
+            },
+            'email_consent': {
+                'type': 'integer'
+            },
+            'gclid_url': {
+                'type': 'string'
+            },
+            'new_account_virtual': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'residence': {
+                'type': 'string'
+            },
+            'signup_device': {
+                'type': 'string'
+            },
+            'type': {
+                'type': 'string'
+            },
+            'utm_ad_id': {},
+            'utm_adgroup_id': {},
+            'utm_adrollclk_id': {},
+            'utm_campaign': {},
+            'utm_campaign_id': {},
+            'utm_content': {},
+            'utm_fbcl_id': {},
+            'utm_gl_client_id': {},
+            'utm_medium': {},
+            'utm_msclk_id': {},
+            'utm_source': {},
+            'utm_term': {},
+            'verification_code': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'new_account_virtual',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def new_account_wallet(self, args=None):
+        """
+        Create a new account real-money wallet account.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                address_city : str
+                    [Optional] Within 35 characters.
+                address_line_1 : str
+                    [Optional] Mailing address.
+                address_line_2 : str
+                    [Optional] Within 70 characters.
+                address_postcode : str
+                    [Optional] Within 20 characters and may not contain '+'.
+                address_state : str
+                    [Optional] Possible value receive from states_list call.
+                currency : str
+                    [Optional] To set currency of the account. List of supported currencies can be acquired with payout_currencies call.
+                date_of_birth : str
+                    [Optional] Date of birth format: yyyy-mm-dd.
+                first_name : str
+                    [Optional] Within 2-50 characters, use only letters, spaces, hyphens, full-stops or apostrophes.
+                last_name : str
+                    [Optional] Within 2-50 characters, use only letters, spaces, hyphens, full-stops or apostrophes.
+                new_account_wallet : int
+                    Must be 1
+                non_pep_declaration : int
+                    [Optional] Indicates client's self-declaration of not being a PEP/RCA (Politically Exposed Person/Relatives and Close Associates).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                payment_method : str
+                    To set method which is used to transfer to/from wallet.
+                phone : str
+                    [Optional] Starting with + followed by 8-35 digits, allowing hyphens or space.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'address_city': {
+                'type': 'string'
+            },
+            'address_line_1': {
+                'type': 'string'
+            },
+            'address_line_2': {
+                'type': 'string'
+            },
+            'address_postcode': {
+                'type': 'string'
+            },
+            'address_state': {
+                'type': 'string'
+            },
+            'currency': {
+                'required': 1,
+                'type': 'string'
+            },
+            'date_of_birth': {
+                'type': 'string'
+            },
+            'first_name': {
+                'type': 'string'
+            },
+            'last_name': {
+                'type': 'string'
+            },
+            'new_account_wallet': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'non_pep_declaration': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'payment_method': {
+                'required': 1,
+                'type': 'string'
+            },
+            'phone': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'new_account_wallet',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def notification_event(self, args=None):
+        """
+        Notify the backend about a specific event.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                args : Any
+                category : str
+                    The category or nature of the event.
+                event : str
+                    The name of the event.
+                notification_event : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'args': {
+                'documents': {}
+            },
+            'category': {
+                'required': 1,
+                'type': 'string'
+            },
+            'event': {
+                'required': 1,
+                'type': 'string'
+            },
+            'notification_event': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'notification_event',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def oauth_apps(self, args=None):
+        """
+        List all my used OAuth applications.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                oauth_apps : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'oauth_apps': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'oauth_apps',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_advert_create(self, args=None):
+        """
+        Creates a P2P (Peer to Peer) advert. Can only be used by an approved P2P advertiser. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                amount : Number
+                    The total amount of the advert, in advertiser's account currency.
+                contact_info : str
+                    [Optional] Advertiser contact information. Only applicable for 'sell adverts'.
+                description : str
+                    [Optional] General information about the advert.
+                local_currency : str
+                    [Optional] Local currency for this advert. If not provided, will use the currency of client's residence by default.
+                max_order_amount : Number
+                    Maximum allowed amount for the orders of this advert, in advertiser's account_currency. Should be less than or equal to total amount of the advert.
+                min_order_amount : Number
+                    Minimum allowed amount for the orders of this advert, in advertiser's account_currency. Should be less than max_order_amount.
+                p2p_advert_create : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                payment_info : str
+                    [Optional] Payment instructions. Only applicable for 'sell adverts'.
+                payment_method : str
+                    Supported payment methods. Separate multiple values with a comma, maximum 3.
+                payment_method_ids : Any
+                    IDs of payment methods, only applicable for sell ads.
+                rate : Number
+                    Conversion rate from advertiser's account currency to local_currency.
+                req_id : int
+                    [Optional] Used to map request to response.
+                type : str
+                    Whether this is a buy or a sell.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'amount': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'contact_info': {
+                'type': 'string'
+            },
+            'description': {
+                'type': 'string'
+            },
+            'local_currency': {
+                'type': 'string'
+            },
+            'max_order_amount': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'min_order_amount': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'p2p_advert_create': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'payment_info': {
+                'type': 'string'
+            },
+            'payment_method': {
+                'type': 'string'
+            },
+            'payment_method_ids': {},
+            'rate': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'type': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_advert_create',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_advert_info(self, args=None):
+        """
+        Retrieve information about a P2P advert. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                id : str
+                    [Optional] The unique identifier for this advert. Optional when subscribe is 1. If not provided, all advertiser adverts will be subscribed.
+                p2p_advert_info : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] If set to 1, will send updates when changes occur. Optional when id is provided.
+                use_client_limits : int
+                    [Optional] If set to 1, the maximum order amount will be adjusted to the current balance and turnover limits of the account.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'id': {
+                'type': 'string'
+            },
+            'p2p_advert_info': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            },
+            'use_client_limits': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_advert_info',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_advert_list(self, args=None):
+        """
+        Returns available adverts for use with `p2p_order_create`. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                advertiser_id : str
+                    [Optional] ID of the advertiser to list adverts for.
+                advertiser_name : str
+                    [Optional] Search for advertiser by name. Partial matches will be returned.
+                amount : Number
+                    [Optional] How much to buy or sell, used to calculate prices.
+                counterparty_type : str
+                    [Optional] Filter the adverts by counterparty_type.
+                favourites_only : int
+                    [Optional] Only show adverts from favourite advertisers. Default is 0.
+                limit : int
+                    [Optional] Used for paging.
+                local_currency : str
+                    [Optional] Currency to conduct payment transaction in, defaults to the main currency for the client's country.
+                offset : int
+                    [Optional] Used for paging.
+                p2p_advert_list : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                payment_method : Any
+                    [Optional] Search by supported payment methods.
+                req_id : int
+                    [Optional] Used to map request to response.
+                sort_by : str
+                    [Optional] How the results are sorted: best rate, or advertiser completion rate.
+                use_client_limits : int
+                    [Optional] If set to 1, ads that exceed this account's balance or turnover limits will not be shown.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'advertiser_id': {
+                'type': 'string'
+            },
+            'advertiser_name': {
+                'type': 'string'
+            },
+            'amount': {
+                'type': 'numeric'
+            },
+            'counterparty_type': {
+                'type': 'string'
+            },
+            'favourites_only': {
+                'type': 'integer'
+            },
+            'limit': {
+                'type': 'integer'
+            },
+            'local_currency': {
+                'type': 'string'
+            },
+            'offset': {
+                'type': 'integer'
+            },
+            'p2p_advert_list': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'payment_method': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'sort_by': {
+                'type': 'string'
+            },
+            'use_client_limits': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_advert_list',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_advert_update(self, args=None):
+        """
+        Updates a P2P advert. Can only be used by the advertiser. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                delete : int
+                    [Optional] If set to 1, permanently deletes the advert.
+                id : str
+                    The unique identifier for this advert.
+                is_active : int
+                    [Optional] Activate or deactivate the advert.
+                p2p_advert_update : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                payment_method : str
+                    [Optional] Supported payment methods. Separate multiple values with a comma, maximum 3.
+                payment_method_ids : Any
+                    [Optional] IDs of payment methods, only applicable for sell ads. Will replace exisiting methods.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'delete': {
+                'type': 'integer'
+            },
+            'id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'is_active': {
+                'type': 'integer'
+            },
+            'p2p_advert_update': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'payment_method': {
+                'type': 'string'
+            },
+            'payment_method_ids': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_advert_update',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_advertiser_adverts(self, args=None):
+        """
+        Returns all P2P adverts created by the authorized client. Can only be used by a registered P2P advertiser. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                limit : int
+                    [Optional] Used for paging. This value will also apply to subsription responses.
+                offset : int
+                    [Optional] Used for paging. This value will also apply to subsription responses.
+                p2p_advertiser_adverts : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'limit': {
+                'type': 'integer'
+            },
+            'offset': {
+                'type': 'integer'
+            },
+            'p2p_advertiser_adverts': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_advertiser_adverts',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_advertiser_create(self, args=None):
+        """
+        Registers the client as a P2P advertiser. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                contact_info : str
+                    [Optional] Advertiser's contact information, to be used as a default for new sell adverts.
+                default_advert_description : str
+                    [Optional] Default description that can be used every time an advert is created.
+                name : str
+                    The advertiser's displayed name.
+                p2p_advertiser_create : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                payment_info : str
+                    [Optional] Advertiser's payment information, to be used as a default for new sell adverts.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] If set to 1, will send updates whenever there is an update to advertiser
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'contact_info': {
+                'type': 'string'
+            },
+            'default_advert_description': {
+                'type': 'string'
+            },
+            'name': {
+                'required': 1,
+                'type': 'string'
+            },
+            'p2p_advertiser_create': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'payment_info': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_advertiser_create',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_advertiser_info(self, args=None):
+        """
+        Retrieve information about a P2P advertiser. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                id : str
+                    [Optional] The unique identifier for this advertiser. If not provided, returns advertiser information about the current account.
+                p2p_advertiser_info : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] If set to 1, will send updates whenever there is an update to advertiser
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'id': {
+                'type': 'string'
+            },
+            'p2p_advertiser_info': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_advertiser_info',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_advertiser_payment_methods(self, args=None):
+        """
+        Manage or list P2P advertiser payment methods.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                create : Any
+                    Contains new payment method entries.
+                delete : Any
+                    Contains payment methods to delete.
+                p2p_advertiser_payment_methods : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                update : Any
+                    Contains payment methods to update.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'create': {},
+            'delete': {},
+            'p2p_advertiser_payment_methods': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'update': {}
+        }
+
+        all_args = {
+            'method': 'p2p_advertiser_payment_methods',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_advertiser_relations(self, args=None):
+        """
+        Updates and returns favourite and blocked advertisers of the current user.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                add_blocked : Any
+                    IDs of advertisers to block.
+                add_favourites : Any
+                    IDs of advertisers to add as favourites.
+                p2p_advertiser_relations : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                remove_blocked : Any
+                    IDs of advertisers to remove from blocked.
+                remove_favourites : Any
+                    IDs of advertisers to remove from favourites.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'add_blocked': {},
+            'add_favourites': {},
+            'p2p_advertiser_relations': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'remove_blocked': {},
+            'remove_favourites': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_advertiser_relations',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_advertiser_update(self, args=None):
+        """
+        Update the information of the P2P advertiser for the current account. Can only be used by an approved P2P advertiser. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                contact_info : str
+                    [Optional] Advertiser's contact information, to be used as a default for new sell adverts.
+                default_advert_description : str
+                    [Optional] Default description that can be used every time an advert is created.
+                is_listed : int
+                    [Optional] Used to set if the advertiser's adverts could be listed. When 0, adverts won't be listed regardless of they are active or not. This doesn't change the is_active of each individual advert.
+                p2p_advertiser_update : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                payment_info : str
+                    [Optional] Advertiser's payment information, to be used as a default for new sell adverts.
+                req_id : int
+                    [Optional] Used to map request to response.
+                show_name : int
+                    [Optional] When 1, the advertiser's real name will be displayed on to other users on adverts and orders.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'contact_info': {
+                'type': 'string'
+            },
+            'default_advert_description': {
+                'type': 'string'
+            },
+            'is_listed': {
+                'type': 'integer'
+            },
+            'p2p_advertiser_update': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'payment_info': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'show_name': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_advertiser_update',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_chat_create(self, args=None):
+        """
+        Creates a P2P chat for the specified order. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                order_id : str
+                    The unique identifier for the order to create the chat for.
+                p2p_chat_create : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'order_id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'p2p_chat_create': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_chat_create',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_order_cancel(self, args=None):
+        """
+        Cancel a P2P order. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                id : str
+                    The unique identifier for this order.
+                p2p_order_cancel : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'p2p_order_cancel': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_order_cancel',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_order_confirm(self, args=None):
+        """
+        Confirm a P2P order. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                id : str
+                    The unique identifier for this order.
+                p2p_order_confirm : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'p2p_order_confirm': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_order_confirm',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_order_create(self, args=None):
+        """
+        Creates a P2P order for the specified advert. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                advert_id : str
+                    The unique identifier for the advert to create an order against.
+                amount : Number
+                    The amount of currency to be bought or sold.
+                contact_info : str
+                    [Optional] Seller contact information. Only applicable for 'sell orders'.
+                p2p_order_create : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                payment_info : str
+                    [Optional] Payment instructions, only applicable for sell orders.
+                payment_method_ids : Any
+                    IDs of payment methods, only applicable for sell orders.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] If set to 1, will send updates whenever there is an update to the order.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'advert_id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'amount': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'contact_info': {
+                'type': 'string'
+            },
+            'p2p_order_create': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'payment_info': {
+                'type': 'string'
+            },
+            'payment_method_ids': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_order_create',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_order_dispute(self, args=None):
+        """
+        Dispute a P2P order. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                dispute_reason : str
+                    The predefined dispute reason
+                id : str
+                    The unique identifier for this order.
+                p2p_order_dispute : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'dispute_reason': {
+                'required': 1,
+                'type': 'string'
+            },
+            'id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'p2p_order_dispute': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_order_dispute',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_order_info(self, args=None):
+        """
+        Retrieves the information about a P2P order. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                id : str
+                    The unique identifier for the order.
+                p2p_order_info : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] If set to 1, will send updates whenever there is an update to order
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'p2p_order_info': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_order_info',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_order_list(self, args=None):
+        """
+        List active orders. **This API call is still in Beta.**
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                active : Number
+                    [Optional] Should be 1 to list active, 0 to list inactive (historical).
+                advert_id : str
+                    [Optional] If present, lists orders applying to a specific advert.
+                limit : int
+                    [Optional] Used for paging.
+                offset : int
+                    [Optional] Used for paging.
+                p2p_order_list : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] If set to 1, will send updates whenever there is a change to any order belonging to you.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'active': {
+                'type': 'numeric'
+            },
+            'advert_id': {
+                'type': 'string'
+            },
+            'limit': {
+                'type': 'integer'
+            },
+            'offset': {
+                'type': 'integer'
+            },
+            'p2p_order_list': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_order_list',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def p2p_payment_methods(self, args=None):
+        """
+        List all P2P payment methods.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                p2p_payment_methods : int
+                    Must be 1
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'p2p_payment_methods': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'p2p_payment_methods',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def payment_methods(self, args=None):
+        """
+        Will return a list payment methods available for the given country. If the request is authenticated the client's residence country will be used.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                country : str
+                    [Optional] 2-letter country code (ISO standard).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                payment_methods : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'country': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'payment_methods': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'payment_methods',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def paymentagent_create(self, args=None):
+        """
+        Saves client's payment agent details.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                affiliate_id : str
+                    [Optional] Client's My Affiliate id, if exists.
+                code_of_conduct_approval : int
+                    Indicates client's agreement with the Code of Conduct.
+                commission_deposit : Number
+                    Commission  (%) the agent wants to take on deposits
+                commission_withdrawal : Number
+                    Commission  (%) the agent wants to take on withdrawals
+                email : str
+                    Payment agent's email address.
+                information : str
+                    [Optional] Information about payment agent and their proposed service.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                payment_agent_name : str
+                    The name with which the payment agent is going to be identified.
+                paymentagent_create : int
+                    Must be 1
+                phone : str
+                    Payment agent's phone number with coutry code.
+                req_id : int
+                    [Optional] Used to map request to response.
+                supported_payment_methods : Any
+                    A list of supported payment methods.
+                url : str
+                    The URL of payment agent's website.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'affiliate_id': {
+                'type': 'string'
+            },
+            'code_of_conduct_approval': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'commission_deposit': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'commission_withdrawal': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'email': {
+                'required': 1,
+                'type': 'string'
+            },
+            'information': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'payment_agent_name': {
+                'required': 1,
+                'type': 'string'
+            },
+            'paymentagent_create': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'phone': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'supported_payment_methods': {
+                'required': 1
+            },
+            'url': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'paymentagent_create',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def paymentagent_details(self, args=None):
+        """
+        Gets client's payment agent details.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                paymentagent_details : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'paymentagent_details': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'paymentagent_details',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def paymentagent_list(self, args=None):
+        """
+        Will return a list of Payment Agents for a given country for a given currency. Payment agents allow users to deposit and withdraw funds using local payment methods that might not be available via the main website's cashier system.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                currency : str
+                    [Optional] If specified, only payment agents that supports that currency will be returned (obtained from payout_currencies call).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                paymentagent_list : str
+                    Client's 2-letter country code (obtained from residence_list call).
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'currency': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'paymentagent_list': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'paymentagent_list',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def paymentagent_transfer(self, args=None):
+        """
+        Payment Agent Transfer - this call is available only to accounts that are approved Payment Agents.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                amount : Number
+                    The amount to transfer.
+                currency : str
+                    Currency code.
+                description : str
+                    [Optional] Remarks about the transfer.
+                dry_run : int
+                    [Optional] If set to 1, just do validation.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                paymentagent_transfer : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+                transfer_to : str
+                    The loginid of the recipient account.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'amount': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'currency': {
+                'required': 1,
+                'type': 'string'
+            },
+            'description': {
+                'type': 'string'
+            },
+            'dry_run': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'paymentagent_transfer': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'transfer_to': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'paymentagent_transfer',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def paymentagent_withdraw(self, args=None):
+        """
+        Initiate a withdrawal to an approved Payment Agent.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                amount : Number
+                    The amount to withdraw to the payment agent.
+                currency : str
+                    The currency code.
+                description : str
+                    [Optional] Remarks about the withdraw. Only letters, numbers, space, period, comma, - ' are allowed.
+                dry_run : int
+                    [Optional] If set to 1, just do validation.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                paymentagent_loginid : str
+                    The payment agent loginid received from the paymentagent_list call.
+                paymentagent_withdraw : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+                verification_code : str
+                    Email verification code (received from a verify_email call, which must be done first)
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'amount': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'currency': {
+                'required': 1,
+                'type': 'string'
+            },
+            'description': {
+                'type': 'string'
+            },
+            'dry_run': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'paymentagent_loginid': {
+                'required': 1,
+                'type': 'string'
+            },
+            'paymentagent_withdraw': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'verification_code': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'paymentagent_withdraw',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def payout_currencies(self, args=None):
+        """
+        Retrieve a list of available option payout currencies. If a user is logged in, only the currencies available for the account will be returned.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                payout_currencies : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'payout_currencies': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'payout_currencies',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def ping(self, args=None):
+        """
+        To send the ping request to the server. Mostly used to test the connection or to keep it alive.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                ping : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'ping': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'ping',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def portfolio(self, args=None):
+        """
+        Receive information about my current portfolio of outstanding options
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                contract_type : Any
+                    Return only contracts of the specified types
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                portfolio : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'contract_type': {},
+            'passthrough': {},
+            'portfolio': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'portfolio',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def profit_table(self, args=None):
+        """
+        Retrieve a summary of account Profit Table, according to given search criteria
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                contract_type : Any
+                    Return only contracts of the specified types
+                date_from : str
+                    [Optional] Start date (epoch or YYYY-MM-DD)
+                date_to : str
+                    [Optional] End date (epoch or YYYY-MM-DD)
+                description : int
+                    [Optional] If set to 1, will return full contracts description.
+                limit : Number
+                    [Optional] Apply upper limit to count of transactions received.
+                offset : Number
+                    [Optional] Number of transactions to skip.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                profit_table : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+                sort : str
+                    [Optional] Sort direction.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'contract_type': {},
+            'date_from': {
+                'type': 'string'
+            },
+            'date_to': {
+                'type': 'string'
+            },
+            'description': {
+                'type': 'integer'
+            },
+            'limit': {
+                'type': 'numeric'
+            },
+            'offset': {
+                'type': 'numeric'
+            },
+            'passthrough': {},
+            'profit_table': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'sort': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'profit_table',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def proposal(self, args=None):
+        """
+        Gets latest price for a specific contract.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                amount : Number
+                    [Optional] Proposed contract payout or stake, or multiplier (for lookbacks).
+                barrier : str
+                    [Optional] Barrier for the contract (or last digit prediction for digit contracts). Contracts less than 24 hours in duration would need a relative barrier (barriers which need +/-), where entry spot would be adjusted accordingly with that amount to define a barrier, except for Synthetic Indices as they support both relative and absolute barriers. Not needed for lookbacks.
+                barrier2 : str
+                    [Optional] Low barrier for the contract (for contracts with two barriers). Contracts less than 24 hours in duration would need a relative barrier (barriers which need +/-), where entry spot would be adjusted accordingly with that amount to define a barrier, except for Synthetic Indices as they support both relative and absolute barriers. Not needed for lookbacks.
+                basis : str
+                    [Optional] Indicates type of the amount.
+                cancellation : str
+                    Cancellation duration option (only for MULTUP and MULTDOWN contracts).
+                contract_type : str
+                    The proposed contract type
+                currency : str
+                    This can only be the account-holder's currency (obtained from payout_currencies call).
+                date_expiry : int
+                    [Optional] Epoch value of the expiry time of the contract. Either date_expiry or duration is required.
+                date_start : int
+                    [Optional] Indicates epoch value of the starting time of the contract. If left empty, the start time of the contract is now.
+                duration : int
+                    [Optional] Duration quantity. Either date_expiry or duration is required.
+                duration_unit : str
+                    [Optional] Duration unit - s: seconds, m: minutes, h: hours, d: days, t: ticks.
+                limit_order : Any
+                multiplier : Number
+                    [Optional] The multiplier for non-binary options. E.g. lookbacks.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                product_type : str
+                    [Optional] The product type.
+                proposal : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+                selected_tick : int
+                    [Optional] The tick that is predicted to have the highest/lowest value - for TICKHIGH and TICKLOW contracts.
+                subscribe : int
+                    [Optional] 1 - to initiate a realtime stream of prices. Note that tick trades (without a user-defined barrier), digit trades and less than 24 hours at-the-money contracts for the following underlying symbols are not streamed: R_10, R_25, R_50, R_75, R_100, RDBULL, RDBEAR (this is because their price is constant).
+                symbol : str
+                    The short symbol name (obtained from active_symbols call).
+                trading_period_start : int
+                    [Optional] Required only for multi-barrier trading. Defines the epoch value of the trading period start time.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'amount': {
+                'type': 'numeric'
+            },
+            'barrier': {
+                'type': 'string'
+            },
+            'barrier2': {
+                'type': 'string'
+            },
+            'basis': {
+                'type': 'string'
+            },
+            'cancellation': {
+                'type': 'string'
+            },
+            'contract_type': {
+                'required': 1,
+                'type': 'string'
+            },
+            'currency': {
+                'required': 1,
+                'type': 'string'
+            },
+            'date_expiry': {
+                'type': 'integer'
+            },
+            'date_start': {
+                'type': 'integer'
+            },
+            'duration': {
+                'type': 'integer'
+            },
+            'duration_unit': {
+                'type': 'string'
+            },
+            'limit_order': {
+                'stop_loss': {
+                    'type': 'numeric'
+                },
+                'take_profit': {
+                    'type': 'numeric'
+                }
+            },
+            'multiplier': {
+                'type': 'numeric'
+            },
+            'passthrough': {},
+            'product_type': {
+                'type': 'string'
+            },
+            'proposal': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'selected_tick': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            },
+            'symbol': {
+                'required': 1,
+                'type': 'string'
+            },
+            'trading_period_start': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'proposal',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def proposal_open_contract(self, args=None):
+        """
+        Get latest price (and other information) for a contract in the user's portfolio
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                contract_id : int
+                    [Optional] Contract ID received from a portfolio request. If not set, you will receive stream of all open contracts.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                proposal_open_contract : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] 1 to stream.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'contract_id': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'proposal_open_contract': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'proposal_open_contract',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def reality_check(self, args=None):
+        """
+        Retrieve summary of client's trades and account for the Reality Check facility. A 'reality check' means a display of time elapsed since the session began, and associated client profit/loss. The Reality Check facility is a regulatory requirement for certain landing companies.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                reality_check : int
+                    Must be 1
+                req_id : int
+                    [Optional] Used to map request to response.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'reality_check': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'req_id': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'reality_check',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def request_report(self, args=None):
+        """
+        Send report to client's registered e-mail
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                date_from : int
+                    Start date of the report
+                date_to : int
+                    End date of the report
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                report_type : str
+                    Type of report to be sent to client's registered e-mail address
+                req_id : int
+                    [Optional] Used to map request to response.
+                request_report : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'date_from': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'date_to': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'report_type': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'request_report': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'request_report',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def reset_password(self, args=None):
+        """
+        Reset Password.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                date_of_birth : str
+                    [Optional] Date of birth format: yyyy-mm-dd. Only required for clients with real-money accounts.
+                new_password : str
+                    New password. For validation (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                reset_password : int
+                    Must be 1
+                verification_code : str
+                    Email verification code (received from a verify_email call, which must be done first)
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'date_of_birth': {
+                'type': 'string'
+            },
+            'new_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'reset_password': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'verification_code': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'reset_password',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def residence_list(self, args=None):
+        """
+        This call returns a list of countries and 2-letter country codes, suitable for populating the account opening form.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                residence_list : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'residence_list': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'residence_list',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def revoke_oauth_app(self, args=None):
+        """
+        Used for revoking access of particular app.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                revoke_oauth_app : int
+                    The application ID to revoke.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'revoke_oauth_app': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'revoke_oauth_app',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def sell(self, args=None):
+        """
+        Sell a Contract as identified from a previous `portfolio` call.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                price : Number
+                    Minimum price at which to sell the contract, or 0 for 'sell at market'.
+                req_id : int
+                    [Optional] Used to map request to response.
+                sell : int
+                    Pass contract_id received from the portfolio call.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'price': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'sell': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'sell',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def sell_contract_for_multiple_accounts(self, args=None):
+        """
+        Sell contracts for multiple accounts simultaneously. Uses the shortcode response from `buy_contract_for_multiple_accounts` to identify the contract, and authorisation tokens to select which accounts to sell those contracts on. Note that only the accounts identified by the tokens will be affected. This will not sell the contract on the currently-authorised account unless you include the token for the current account.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                price : Number
+                    Minimum price at which to sell the contract, or 0 for 'sell at market'.
+                req_id : int
+                    [Optional] Used to map request to response.
+                sell_contract_for_multiple_accounts : int
+                    Must be 1
+                shortcode : str
+                    An internal ID used to identify the contract which was originally bought. This is returned from the buy and buy_contract_for_multiple_accounts calls.
+                tokens : Any
+                    Authorisation tokens which select the accounts to sell use for the affected accounts.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'price': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'sell_contract_for_multiple_accounts': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'shortcode': {
+                'required': 1,
+                'type': 'string'
+            },
+            'tokens': {
+                'required': 1
+            }
+        }
+
+        all_args = {
+            'method': 'sell_contract_for_multiple_accounts',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def sell_expired(self, args=None):
+        """
+        This call will try to sell any expired contracts and return the number of sold contracts.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                sell_expired : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'sell_expired': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'sell_expired',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def service_token(self, args=None):
+        """
+        Retrieves the authorization token for the specified service.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                country : str
+                    [Optional] The 2-letter country code.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                referrer : str
+                    [Optional] The URL of the web page where the Web SDK will be used.
+                req_id : int
+                    [Optional] Used to map request to response.
+                server : str
+                    Server (dxtrade only).
+                service : Any
+                    The service(s) to retrieve token(s) for.
+                service_token : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'country': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'referrer': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'server': {
+                'type': 'string'
+            },
+            'service': {
+                'required': 1
+            },
+            'service_token': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'service_token',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def set_account_currency(self, args=None):
+        """
+        Set account currency, this will be default currency for your account i.e currency for trading, deposit. Please note that account currency can only be set once, and then can never be changed.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                set_account_currency : str
+                    Currency of the account. List of supported currencies can be acquired with payout_currencies call.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'set_account_currency': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'set_account_currency',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def set_financial_assessment(self, args=None):
+        """
+        This call sets the financial assessment details based on the client's answers to analyze whether they possess the experience and knowledge to understand the risks involved with binary options trading.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_turnover : str
+                    [Optional] The anticipated account turnover.
+                binary_options_trading_experience : str
+                    [Optional] Binary options trading experience.
+                binary_options_trading_frequency : str
+                    [Optional] Binary options trading frequency.
+                cfd_trading_experience : str
+                    [Optional] CFDs trading experience.
+                cfd_trading_frequency : str
+                    [Optional] CFDs trading frequency.
+                education_level : str
+                    Level of Education.
+                employment_industry : str
+                    Industry of Employment.
+                employment_status : str
+                    [Optional] Employment Status.
+                estimated_worth : str
+                    Estimated Net Worth.
+                forex_trading_experience : str
+                    [Optional] Forex trading experience.
+                forex_trading_frequency : str
+                    [Optional] Forex trading frequency.
+                income_source : str
+                    Income Source.
+                net_income : str
+                    Net Annual Income.
+                occupation : str
+                    Occupation.
+                other_instruments_trading_experience : str
+                    [Optional] Trading experience in other financial instruments.
+                other_instruments_trading_frequency : str
+                    [Optional] Trading frequency in other financial instruments.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                set_financial_assessment : int
+                    Must be 1
+                source_of_wealth : str
+                    [Optional] Source of wealth.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_turnover': {
+                'type': 'string'
+            },
+            'binary_options_trading_experience': {
+                'type': 'string'
+            },
+            'binary_options_trading_frequency': {
+                'type': 'string'
+            },
+            'cfd_trading_experience': {
+                'type': 'string'
+            },
+            'cfd_trading_frequency': {
+                'type': 'string'
+            },
+            'education_level': {
+                'required': 1,
+                'type': 'string'
+            },
+            'employment_industry': {
+                'required': 1,
+                'type': 'string'
+            },
+            'employment_status': {
+                'type': 'string'
+            },
+            'estimated_worth': {
+                'required': 1,
+                'type': 'string'
+            },
+            'forex_trading_experience': {
+                'type': 'string'
+            },
+            'forex_trading_frequency': {
+                'type': 'string'
+            },
+            'income_source': {
+                'required': 1,
+                'type': 'string'
+            },
+            'net_income': {
+                'required': 1,
+                'type': 'string'
+            },
+            'occupation': {
+                'required': 1,
+                'type': 'string'
+            },
+            'other_instruments_trading_experience': {
+                'type': 'string'
+            },
+            'other_instruments_trading_frequency': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'set_financial_assessment': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'source_of_wealth': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'set_financial_assessment',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def set_self_exclusion(self, args=None):
+        """
+        Set Self-Exclusion (this call should be used in conjunction with `get_self_exclusion`)
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                exclude_until : Any
+                    [Optional] Exclude me from the website (for a minimum of 6 months, up to a maximum of 5 years). Note: uplifting this self-exclusion may require contacting the company.
+                max_30day_deposit : Any
+                    [Optional] 7-day limit on deposits.
+                max_30day_losses : Any
+                    [Optional] 30-day limit on losses.
+                max_30day_turnover : Any
+                    [Optional] 30-day turnover limit.
+                max_7day_deposit : Any
+                    [Optional] 7-day limit on deposits.
+                max_7day_losses : Any
+                    [Optional] 7-day limit on losses.
+                max_7day_turnover : Any
+                    [Optional] 7-day turnover limit.
+                max_balance : Any
+                    [Optional] Maximum account cash balance.
+                max_deposit : Any
+                    [Optional] Daily deposit limit.
+                max_losses : Any
+                    [Optional] Daily limit on losses.
+                max_open_bets : Any
+                    [Optional] Maximum number of open positions.
+                max_turnover : Any
+                    [Optional] Daily turnover limit.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                session_duration_limit : Any
+                    [Optional] Session duration limit, in minutes.
+                set_self_exclusion : int
+                    Must be 1
+                timeout_until : Any
+                    [Optional] Exclude me from the website (for up to 6 weeks). Requires time in epoch format. Note: unlike exclude_until, this self-exclusion will be lifted automatically at the expiry of the timeout period.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'exclude_until': {},
+            'max_30day_deposit': {},
+            'max_30day_losses': {},
+            'max_30day_turnover': {},
+            'max_7day_deposit': {},
+            'max_7day_losses': {},
+            'max_7day_turnover': {},
+            'max_balance': {},
+            'max_deposit': {},
+            'max_losses': {},
+            'max_open_bets': {},
+            'max_turnover': {},
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'session_duration_limit': {},
+            'set_self_exclusion': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'timeout_until': {}
+        }
+
+        all_args = {
+            'method': 'set_self_exclusion',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def set_settings(self, args=None):
+        """
+        Set User Settings (this call should be used in conjunction with `get_settings`)
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_opening_reason : str
+                    [Optional] Purpose and reason for requesting the account opening. Only applicable for real money account. Required for clients that have not set it yet. Can only be set once.
+                address_city : str
+                    [Optional] Note: not applicable for virtual account. Required field for real money account.
+                address_line_1 : str
+                    [Optional] Note: not applicable for virtual account. Required field for real money account.
+                address_line_2 : Any
+                    [Optional] Note: not applicable for virtual account. Optional field for real money account.
+                address_postcode : str
+                    [Optional] Note: not applicable for virtual account. Optional field for real money account.
+                address_state : str
+                    [Optional] Note: not applicable for virtual account. Optional field for real money account.
+                allow_copiers : int
+                    [Optional] Boolean value 1 or 0, indicating permission to allow others to follow your trades. Note: not applicable for Virtual account. Only allow for real money account.
+                citizen : Any
+                    [Optional] Country of legal citizenship, 2-letter country code.
+                date_of_birth : str
+                    [Optional] Date of birth format: yyyy-mm-dd (can only be changed on unauthenticated svg accounts).
+                email_consent : int
+                    [Optional] Boolean value 1 or 0, indicating permission to use email address for any contact which may include marketing
+                feature_flag : Any
+                first_name : str
+                    [Optional] Within 2-50 characters, use only letters, spaces, hyphens, full-stops or apostrophes (can only be changed on unauthenticated svg accounts).
+                last_name : str
+                    [Optional] Within 2-50 characters, use only letters, spaces, hyphens, full-stops or apostrophes (can only be changed on unauthenticated svg accounts).
+                non_pep_declaration : int
+                    [Optional] Indicates client's self-declaration of not being a PEP/RCA (Politically Exposed Person/Relatives and Close Associates). Effective for real accounts only.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                phone : Any
+                    [Optional] Note: not applicable for virtual account. Starting with + followed by 9-35 digits, hyphens or space.
+                place_of_birth : str
+                    [Optional] Place of birth, 2-letter country code.
+                preferred_language : Any
+                    [Optional] User's preferred language, ISO standard language code
+                req_id : int
+                    [Optional] Used to map request to response.
+                request_professional_status : int
+                    [Optional] Required when client wants to be treated as professional. Applicable for financial accounts only.
+                residence : Any
+                    [Optional] 2-letter country code. Note: not applicable for real money account. Only allow for Virtual account without residence set.
+                salutation : str
+                    [Optional] Accept any value in enum list (can only be changed on unauthenticated svg accounts).
+                secret_answer : str
+                    [Optional] Answer to secret question, within 4-50 characters. Required for new account and existing client details will be used if client opens another account.
+                secret_question : str
+                    [Optional] Accept any value in enum list. Required for new account and existing client details will be used if client opens another account.
+                set_settings : int
+                    Must be 1
+                tax_identification_number : str
+                    [Optional] Tax identification number. Only applicable for real money account. Required for maltainvest landing company.
+                tax_residence : str
+                    [Optional] Residence for tax purpose. Comma separated iso country code if multiple jurisdictions. Only applicable for real money account. Required for maltainvest landing company.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_opening_reason': {
+                'type': 'string'
+            },
+            'address_city': {
+                'type': 'string'
+            },
+            'address_line_1': {
+                'type': 'string'
+            },
+            'address_line_2': {},
+            'address_postcode': {
+                'type': 'string'
+            },
+            'address_state': {
+                'type': 'string'
+            },
+            'allow_copiers': {
+                'type': 'integer'
+            },
+            'citizen': {},
+            'date_of_birth': {
+                'type': 'string'
+            },
+            'email_consent': {
+                'type': 'integer'
+            },
+            'feature_flag': {
+                'wallet': {
+                    'type': 'integer'
+                }
+            },
+            'first_name': {
+                'type': 'string'
+            },
+            'last_name': {
+                'type': 'string'
+            },
+            'non_pep_declaration': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'phone': {},
+            'place_of_birth': {
+                'type': 'string'
+            },
+            'preferred_language': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'request_professional_status': {
+                'type': 'integer'
+            },
+            'residence': {},
+            'salutation': {
+                'type': 'string'
+            },
+            'secret_answer': {
+                'type': 'string'
+            },
+            'secret_question': {
+                'type': 'string'
+            },
+            'set_settings': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'tax_identification_number': {
+                'type': 'string'
+            },
+            'tax_residence': {
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'set_settings',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def statement(self, args=None):
+        """
+        Retrieve a summary of account transactions, according to given search criteria
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                action_type : str
+                    [Optional] To filter the statement according to the type of transaction.
+                date_from : int
+                    [Optional] Start date (epoch)
+                date_to : int
+                    [Optional] End date (epoch)
+                description : int
+                    [Optional] If set to 1, will return full contracts description.
+                limit : Number
+                    [Optional] Maximum number of transactions to receive.
+                offset : Number
+                    [Optional] Number of transactions to skip.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                statement : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'action_type': {
+                'type': 'string'
+            },
+            'date_from': {
+                'type': 'integer'
+            },
+            'date_to': {
+                'type': 'integer'
+            },
+            'description': {
+                'type': 'integer'
+            },
+            'limit': {
+                'type': 'numeric'
+            },
+            'offset': {
+                'type': 'numeric'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'statement': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'statement',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def states_list(self, args=None):
+        """
+        For a given country, returns a list of States of that country. This is useful to populate the account opening form.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                states_list : str
+                    Client's 2-letter country code (obtained from residence_list call)
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'states_list': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'states_list',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def ticks(self, args=None):
+        """
+        Initiate a continuous stream of spot price updates for a given symbol.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] If set to 1, will send updates whenever a new tick is received.
+                ticks : Any
+                    The short symbol name or array of symbols (obtained from active_symbols call).
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            },
+            'ticks': {
+                'required': 1
+            }
+        }
+
+        all_args = {
+            'method': 'ticks',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def ticks_history(self, args=None):
+        """
+        Get historic tick data for a given symbol.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                adjust_start_time : int
+                    [Optional] 1 - if the market is closed at the end time, or license limit is before end time, adjust interval backwards to compensate.
+                count : int
+                    [Optional] An upper limit on ticks to receive.
+                end : str
+                    Epoch value representing the latest boundary of the returned ticks. If latest is specified, this will be the latest available timestamp.
+                granularity : int
+                    [Optional] Only applicable for style: candles. Candle time-dimension width setting. (default: 60).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                start : int
+                    [Optional] Epoch value representing the earliest boundary of the returned ticks. 
+- For "style": "ticks": this will default to 1 day ago.
+- For "style": "candles": it will default to 1 day ago if count or granularity is undefined.
+                style : str
+                    [Optional] The tick-output style.
+                subscribe : int
+                    [Optional] 1 - to send updates whenever a new tick is received.
+                ticks_history : str
+                    Short symbol name (obtained from the active_symbols call).
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'adjust_start_time': {
+                'type': 'integer'
+            },
+            'count': {
+                'type': 'integer'
+            },
+            'end': {
+                'required': 1,
+                'type': 'string'
+            },
+            'granularity': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'start': {
+                'type': 'integer'
+            },
+            'style': {
+                'type': 'string'
+            },
+            'subscribe': {
+                'type': 'integer'
+            },
+            'ticks_history': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'ticks_history',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def time(self, args=None):
+        """
+        Request back-end server epoch time.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                time : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'time': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'time',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def tnc_approval(self, args=None):
+        """
+        To approve the latest version of terms and conditions.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                affiliate_coc_agreement : int
+                    [Optional] For Affiliate's Code of Conduct Agreement.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                tnc_approval : Number
+                    Must be 1
+                ukgc_funds_protection : int
+                    [Optional] For ASK_UK_FUNDS_PROTECTION in cashier.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'affiliate_coc_agreement': {
+                'type': 'integer'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'tnc_approval': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'ukgc_funds_protection': {
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'tnc_approval',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def topup_virtual(self, args=None):
+        """
+        When a virtual-money's account balance becomes low, it can be topped up using this call.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                topup_virtual : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'topup_virtual': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'topup_virtual',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_durations(self, args=None):
+        """
+        Retrieve a list of all available underlyings and the corresponding contract types and trading duration boundaries. If the user is logged in, only the assets available for that user's landing company will be returned.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                landing_company : str
+                    [Optional] If specified, will return only the underlyings for the specified landing company.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                trading_durations : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'landing_company': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'trading_durations': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_durations',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_platform_accounts(self, args=None):
+        """
+        Get list of Trading Platform accounts for client
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                platform : str
+                    Trading platform name
+                req_id : int
+                    [Optional] Used to map request to response.
+                trading_platform_accounts : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'platform': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'trading_platform_accounts': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_platform_accounts',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_platform_deposit(self, args=None):
+        """
+        Deposit funds from wallet account to trading platform account
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                amount : Number
+                    Amount to deposit (in the currency of from_wallet).
+                from_account : str
+                    Wallet account to transfer money from.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                platform : str
+                    Name of trading platform.
+                req_id : int
+                    [Optional] Used to map request to response.
+                to_account : str
+                    Trading account login to deposit money to.
+                trading_platform_deposit : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'amount': {
+                'type': 'numeric'
+            },
+            'from_account': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'platform': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'to_account': {
+                'required': 1,
+                'type': 'string'
+            },
+            'trading_platform_deposit': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_platform_deposit',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_platform_investor_password_change(self, args=None):
+        """
+        Change the Trading Platform investor password
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_id : str
+                    Trading account ID.
+                new_password : str
+                    New investor password. Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address.
+                old_password : str
+                    Old investor password for validation (non-empty string, accepts any printable ASCII character)
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                platform : str
+                    Name of trading platform.
+                req_id : int
+                    [Optional] Used to map request to response.
+                trading_platform_investor_password_change : Number
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'new_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'old_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'platform': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'trading_platform_investor_password_change': {
+                'required': 1,
+                'type': 'numeric'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_platform_investor_password_change',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_platform_investor_password_reset(self, args=None):
+        """
+        Reset the investor password of a Trading Platform Account
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_id : str
+                    Trading account ID.
+                new_password : str
+                    New password of the account. For validation (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                platform : str
+                    Name of trading platform.
+                req_id : int
+                    [Optional] Used to map request to response.
+                trading_platform_investor_password_reset : int
+                    Must be 1
+                verification_code : str
+                    Email verification code (received from a verify_email call, which must be done first)
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_id': {
+                'required': 1,
+                'type': 'string'
+            },
+            'new_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'platform': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'trading_platform_investor_password_reset': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'verification_code': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_platform_investor_password_reset',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_platform_new_account(self, args=None):
+        """
+        This call creates new Trading account, either demo or real money.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_type : str
+                    Account type.
+                currency : str
+                    [Optional] Trading account currency, the default value will be the qualified account currency.
+                dry_run : int
+                    [Optional] If set to 1, only validation is performed.
+                market_type : str
+                    Market type
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                password : str
+                    The master password of the account. For validation (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address). This field is required.
+                platform : str
+                    Name of trading platform.
+                req_id : int
+                    [Optional] Used to map request to response.
+                sub_account_type : str
+                    [Optional] Sub account type.
+                trading_platform_new_account : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_type': {
+                'required': 1,
+                'type': 'string'
+            },
+            'currency': {
+                'type': 'string'
+            },
+            'dry_run': {
+                'type': 'integer'
+            },
+            'market_type': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'platform': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'sub_account_type': {
+                'type': 'string'
+            },
+            'trading_platform_new_account': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_platform_new_account',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_platform_password_change(self, args=None):
+        """
+        Change the Trading Platform password
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                new_password : str
+                    New trading password. Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address.
+                old_password : str
+                    Old password for validation. Must be empty if a password has not been set yet.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                platform : str
+                    Name of trading platform.
+                req_id : int
+                    [Optional] Used to map request to response.
+                trading_platform_password_change : Number
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'new_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'old_password': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'platform': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'trading_platform_password_change': {
+                'required': 1,
+                'type': 'numeric'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_platform_password_change',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_platform_password_reset(self, args=None):
+        """
+        Reset the password of a Trading Platform Account
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                new_password : str
+                    New password of the account. For validation (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address).
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                platform : str
+                    Name of trading platform.
+                req_id : int
+                    [Optional] Used to map request to response.
+                trading_platform_password_reset : int
+                    Must be 1
+                verification_code : str
+                    Email verification code (received from a verify_email call, which must be done first)
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'new_password': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'platform': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'trading_platform_password_reset': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'verification_code': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_platform_password_reset',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_platform_withdrawal(self, args=None):
+        """
+        This call allows withdrawal from Trading account to a wallet account.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                amount : Number
+                    Amount to withdraw (in the currency of the Trading account).
+                from_account : str
+                    Trading account login to withdraw money from.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                platform : str
+                    Name of trading platform.
+                req_id : int
+                    [Optional] Used to map request to response.
+                to_account : str
+                    Wallet account loginid to transfer money to.
+                trading_platform_withdrawal : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'amount': {
+                'required': 1,
+                'type': 'numeric'
+            },
+            'from_account': {
+                'required': 1,
+                'type': 'string'
+            },
+            'passthrough': {},
+            'platform': {
+                'required': 1,
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'to_account': {
+                'required': 1,
+                'type': 'string'
+            },
+            'trading_platform_withdrawal': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_platform_withdrawal',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_servers(self, args=None):
+        """
+        Get the list of servers for a trading platform.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_type : str
+                    [Optional] Trading account type.
+                environment : str
+                    [Optional] Pass the environment (installation) instance. Currently, there are one demo and two real environments. Defaults to 'all'.
+                market_type : str
+                    [Optional] Market type.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                platform : str
+                    [Optional] Pass the trading platform name, default to mt5
+                req_id : int
+                    [Optional] Used to map request to response.
+                trading_servers : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_type': {
+                'type': 'string'
+            },
+            'environment': {
+                'type': 'string'
+            },
+            'market_type': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'platform': {
+                'type': 'string'
+            },
+            'req_id': {
+                'type': 'integer'
+            },
+            'trading_servers': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_servers',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def trading_times(self, args=None):
+        """
+        Receive a list of market opening times for a given date.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                trading_times : str
+                    Date to receive market opening times for. (yyyy-mm-dd format. today can also be specified).
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'trading_times': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'trading_times',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def transaction(self, args=None):
+        """
+        Subscribe to transaction notifications
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    If set to 1, will send updates whenever there is an update to transactions. If not to 1 then it will not return any records.
+                transaction : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'required': 1,
+                'type': 'integer'
+            },
+            'transaction': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'transaction',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def transfer_between_accounts(self, args=None):
+        """
+        This call allows transfers between accounts held by a given user. Transfer funds between your fiat and cryptocurrency accounts (for a fee). Please note that account_from should be same as current authorized account.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                account_from : str
+                    [Optional] The loginid of the account to transfer funds from.
+                account_to : str
+                    [Optional] The loginid of the account to transfer funds to.
+                accounts : str
+                    [Optional] To control the list of accounts returned when account_from or account_to is not provided. brief (default value) means that accounts with mt5 account_type will be excluded; it will run faster. all means that all accounts with any account_type (including mt5) will be returned.
+                amount : Number
+                    [Optional] The amount to transfer.
+                currency : str
+                    [Optional] Currency code.
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                transfer_between_accounts : int
+                    If account_from or account_to is not provided, it just returns the available accounts.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'account_from': {
+                'type': 'string'
+            },
+            'account_to': {
+                'type': 'string'
+            },
+            'accounts': {
+                'type': 'string'
+            },
+            'amount': {
+                'type': 'numeric'
+            },
+            'currency': {
+                'type': 'string'
+            },
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'transfer_between_accounts': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'transfer_between_accounts',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def verify_email(self, args=None):
+        """
+        Verify an email address for various purposes. The system will send an email to the address containing a security code for verification.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                type : str
+                    Purpose of the email verification call.
+                url_parameters : Any
+                verify_email : str
+                    Email address to be verified.
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'type': {
+                'required': 1,
+                'type': 'string'
+            },
+            'url_parameters': {
+                'affiliate_token': {
+                    'type': 'string'
+                },
+                'date_first_contact': {
+                    'type': 'string'
+                },
+                'gclid_url': {
+                    'type': 'string'
+                },
+                'pa_amount': {
+                    'type': 'numeric'
+                },
+                'pa_currency': {
+                    'type': 'string'
+                },
+                'pa_loginid': {
+                    'type': 'string'
+                },
+                'pa_remarks': {
+                    'type': 'string'
+                },
+                'redirect_to': {
+                    'type': 'integer'
+                },
+                'signup_device': {
+                    'type': 'string'
+                },
+                'utm_ad_id': {},
+                'utm_adgroup_id': {},
+                'utm_adrollclk_id': {},
+                'utm_campaign': {},
+                'utm_campaign_id': {},
+                'utm_content': {},
+                'utm_fbcl_id': {},
+                'utm_gl_client_id': {},
+                'utm_medium': {},
+                'utm_msclk_id': {},
+                'utm_source': {},
+                'utm_term': {}
+            },
+            'verify_email': {
+                'required': 1,
+                'type': 'string'
+            }
+        }
+
+        all_args = {
+            'method': 'verify_email',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def website_status(self, args=None):
+        """
+        Request server status.
+
+        Parameters:
+        -----------
+            args : dict with following keys
+                passthrough : Any
+                    [Optional] Used to pass data through the websocket, which may be retrieved via the echo_req output field.
+                req_id : int
+                    [Optional] Used to map request to response.
+                subscribe : int
+                    [Optional] 1 to stream the server/website status updates.
+                website_status : int
+                    Must be 1
+        """
+
+        if args is None:
+            args = {}
+
+        config = {
+            'passthrough': {},
+            'req_id': {
+                'type': 'integer'
+            },
+            'subscribe': {
+                'type': 'integer'
+            },
+            'website_status': {
+                'required': 1,
+                'type': 'integer'
+            }
+        }
+
+        all_args = {
+            'method': 'website_status',
+            'needs_method_arg': '1',
+            'args': args,
+            'config': config,
+        }
+
+        return await self.process_request(all_args)
+
+    async def process_request(self, all_args):
+        """
+        Process request
+        """
+
+        config = all_args['config']
+        parsed_args = parse_args(all_args)
+        error = validate_args(config=config, args=parsed_args)
+        if error:
+            raise ValueError(error)
+        return await self.send(parsed_args)
+
+__pdoc__ = {
+    'parse_args' : False,
+    'validate_args' : False,
+    'deriv_api.deriv_api_calls.DerivAPICalls.process_request' : False
+}
+
+def parse_args(all_args):
+    """
+    Parse request args
+    """
+
+    parsed_args = all_args['args']
+    method = all_args['method']
+    
+    if all_args['needs_method_arg'] and not(isinstance(parsed_args, dict)):
+        parsed_args = {method: parsed_args}
+
+    parsed_args[method] = parsed_args.get(method, 1)
+    
+    config = all_args['config']
+    for param in parsed_args:
+        value = parsed_args[param]
+        if not (param in config):
+            return
+
+        ptype = config[param].get('type')
+        if ptype and ptype == 'string':
+            parsed_args[param] = f'{value}'
+        elif ptype and (ptype == 'numeric' or ptype == 'boolean'):
+            parsed_args[param] = int(float(value))
+            
+    return parsed_args
+
+
+type_checkers = {
+    'dict': lambda value: isinstance(value, dict),
+    'numeric': lambda value: isinstance(value, Number),
+    'string': lambda value: isinstance(value, str),
+    'boolean': lambda value: value in [True, False, 0, 1],
+    'integer': lambda value: isinstance(value, int)
+}
+
+
+def validate_args(config, args):
+    """
+    Validate request args
+    """
+
+    if not isinstance(args, dict):
+        return f"Requires an dict but a {type(args)} is passed."
+
+    error_messages = []
+    missing = [k for k in config.keys() if (config.get(k) or {}).get('required') and not (k in args)]
+    if len(missing):
+        error_messages.append(f'Required parameters missing: {", ".join(missing)}')
+
+    for param in args.keys():
+        value = args[param]
+        if param not in config:
+            continue
+        expected_type = config[param].get('type')
+
+        if not expected_type:
+            continue
+
+        checker = type_checkers.get(expected_type)
+        if not checker or not checker(value):
+            error_messages.append(f'{expected_type} value expected but found {type(value)}: {param}')
+
+    return ' - '.join(error_messages) if len(error_messages) else ''
diff --git a/deriv_api/easy_future.py b/deriv_api/easy_future.py
new file mode 100644
index 0000000..10f32b6
--- /dev/null
+++ b/deriv_api/easy_future.py
@@ -0,0 +1,120 @@
+from __future__ import annotations
+import asyncio
+from asyncio import Future, CancelledError, InvalidStateError
+from typing import Any, Optional, TypeVar, Union, Callable
+import weakref
+
+_S = TypeVar("_S")
+
+
+class EasyFuture(Future):
+    """A class that extend asyncio Future class and has some more convenient methods
+    Just like Promise in JS or Future in Perl"""
+    def __init__(self, *, loop: Optional[asyncio.AbstractEventLoop] = None, label: Optional[str] = None) -> None:
+        super().__init__(loop=loop)
+        if not label:
+            label = f"Future {id(self)}"
+        self.label = label
+
+    @classmethod
+    def wrap(cls, future: Future) -> EasyFuture:
+        """Wrap an Asyncio Future to a EasyFuture"""
+        if isinstance(future, cls):
+            return future
+
+        easy_future = cls(loop=future.get_loop())
+        easy_future.cascade(future)
+        weak_future = weakref.ref(future)
+
+        def cancel_cb(cb_future: Future):
+            out_future = weak_future()
+            if cb_future.cancelled() and not out_future.done():
+                try:
+                    cb_future.result()
+                except CancelledError as err:
+                    out_future.cancel(*err.args)
+
+        easy_future.add_done_callback(cancel_cb)
+        return easy_future
+
+    def resolve(self, *args: Any) -> EasyFuture:
+        """Set result on the future"""
+        super().set_result(*args)
+        return self
+
+    def reject(self, *args: Union[type, BaseException]) -> EasyFuture:
+        """Set exception on the future"""
+        super().set_exception(*args)
+        return self
+
+    def is_pending(self) -> bool:
+        """Check if the future is pending (not done)"""
+        return not self.done()
+
+    def is_resolved(self) -> bool:
+        """check if the future is resolved (result set)"""
+        return self.done() and not self.cancelled() and not self.exception()
+
+    def is_rejected(self) -> bool:
+        """check if the future is rejected (exception set)"""
+        return self.done() and not self.cancelled() and self.exception()
+
+    def is_cancelled(self) -> bool:
+        """check if the future is cancelled"""
+        return self.cancelled()
+
+    def cascade(self, future: Future) -> EasyFuture:
+        """copy another future result to itself"""
+        if self.done():
+            raise InvalidStateError('invalid state')
+
+        def done_callback(f: Future) -> None:
+            try:
+                result = f.result()
+                self.set_result(result)
+            except CancelledError as err:
+                self.cancel(*err.args)
+            except BaseException as err:
+                self.set_exception(err)
+
+        future.add_done_callback(done_callback)
+        return self
+
+    def then(self, then_callback: Union[Callable[[Any], Any], None], else_callback: Union[Callable[[Any], Any], None] = None) -> EasyFuture:
+        """Simulate Perl Future's 'then' function.
+        Parameters:
+        then_callback: the cb function that will be called when the original Future is resolved
+        else_callback: the cb function that will be called when the original Future is rejected,
+            can be None
+
+        Both cb function should return a Future. The Future returned by the function 'then'
+        will have same result of cb returned Future.
+        """
+        new_future = EasyFuture(loop=self.get_loop())
+
+        def done_callback(myself: EasyFuture) -> None:
+            f: Optional[EasyFuture] = None
+            if myself.is_cancelled():
+                new_future.cancel('Upstream future cancelled')
+                return
+
+            if myself.is_rejected() and else_callback:
+                f = else_callback(myself.exception())
+            elif myself.is_resolved() and then_callback:
+                f = then_callback(myself.result())
+
+            if f is None:
+                new_future.cascade(myself)
+                return
+
+            def inside_callback(internal_future: EasyFuture) -> None:
+                new_future.cascade(internal_future)
+
+            f.add_done_callback(inside_callback)
+
+        self.add_done_callback(done_callback)
+        return new_future
+
+    def catch(self, else_callback: Callable[[_S], Any]) -> EasyFuture:
+        """An variant of 'then' function. it can only get an 'else_cb' which will be run when the future rejected"""
+        return self.then(None, else_callback)
diff --git a/deriv_api/errors.py b/deriv_api/errors.py
new file mode 100644
index 0000000..ab83db0
--- /dev/null
+++ b/deriv_api/errors.py
@@ -0,0 +1,42 @@
+def error_factory(class_type: str) -> object:
+    class GenericError(Exception):
+        def __init__(self, message: str):
+            super().__init__(message)
+            self.type = class_type
+            self.message = message
+
+        def __str__(self) -> str:
+            return f'{self.type}:{self.message}'
+
+    return GenericError
+
+
+class APIError(error_factory('APIError')):
+    pass
+
+
+class ConstructionError(error_factory('ConstructionError')):
+    pass
+
+
+class ResponseError(Exception):
+    def __init__(self, response: dict):
+        super().__init__(response['error']['message'])
+        self.request = response['echo_req']
+        self.code = response['error']['code']
+        self.message = response['error']['message']
+        self.msg_type = response['msg_type']
+        self.req_id = response.get('req_id')
+
+    def __str__(self) -> str:
+        return f"ResponseError: {self.message}"
+
+
+class AddedTaskError(Exception):
+    def __init__(self, exception, name):
+        super().__init__()
+        self.exception = exception
+        self.name = name
+
+    def __str__(self) -> str:
+        return f"{self.name}: {str(self.exception)}"
diff --git a/deriv_api/in_memory.py b/deriv_api/in_memory.py
new file mode 100644
index 0000000..85137a0
--- /dev/null
+++ b/deriv_api/in_memory.py
@@ -0,0 +1,79 @@
+
+__pdoc__ = {
+    'deriv_api.in_memory.InMemory.get': False,
+    'deriv_api.in_memory.InMemory.get_by_msg_type': False,
+    'deriv_api.in_memory.InMemory.has': False,
+    'deriv_api.in_memory.InMemory.set': False
+}
+
+
+class InMemory:
+    """An in memory storage which can be used for caching"""
+
+    def __init__(self) -> None:
+        self.store = {}
+        self.type_store = {}
+
+    def has(self, key: bytes) -> bool:
+        """
+        Check the key exists in the store and returns true if exists.
+
+        Parameters
+        ----------
+        key : bytes
+            Request object key
+
+        Returns
+        -------
+            Returns true if the request key exists in memory
+        """
+        return key in self.store
+
+    # we should serialize key (utils/dict_to_cache_key) before we store it
+    # At first I want to use it directly here.
+    # But from js version of deriv-api logic, user can choose cache object freely.
+    # So we shouldn't suppose other cache module will serialize the key.
+    # So we should always call serialize in the caller module
+    def get(self, key: bytes) -> dict:
+        """
+        Get the response stored in for the given request by key
+
+        Parameters
+        ----------
+        key : bytes
+            Request of object key
+
+        Returns
+        -------
+            Response object received and stored in for the given request key
+        """
+        return self.store[key]
+
+    def get_by_msg_type(self, msg_type: str) -> dict:
+        """
+        Get the response stored in based on message type
+
+        Parameters
+        ----------
+        msg_type : str
+            Request object msg_type
+
+        Returns
+        -------
+            Response object stored in for the given message type
+        """
+        return self.type_store.get(msg_type)
+
+    def set(self, key: bytes, value: dict) -> None:
+        """
+        Stores the response of the given request
+
+        Parameters
+        ----------
+        key : bytes
+            Request object key
+        value : dict
+            Response object received from api
+        """
+        self.store[key] = value
+        self.type_store[value['msg_type']] = value
diff --git a/deriv_api/subscription_manager.py b/deriv_api/subscription_manager.py
new file mode 100644
index 0000000..b9e5250
--- /dev/null
+++ b/deriv_api/subscription_manager.py
@@ -0,0 +1,348 @@
+from deriv_api.utils import dict_to_cache_key
+from deriv_api.errors import APIError
+from rx import operators as op
+from rx.subject import Subject
+from rx import Observable
+from typing import Optional, Union
+
+# streams_list is the list of subscriptions msg_types available.
+# Please add / remove based on current available streams in api.
+# Refer https://developers.binary.com/
+# TODO NEXT auto generate this one
+streams_list = ['balance', 'candles', 'p2p_advertiser', 'p2p_order', 'proposal',
+                'proposal_array', 'proposal_open_contract', 'ticks', 'ticks_history', 'transaction',
+                'website_status', 'buy']
+
+__pdoc__ = {
+    'deriv_api.subscription_manager.SubscriptionManager.complete_subs_by_ids': False,
+    'deriv_api.subscription_manager.SubscriptionManager.complete_subs_by_key': False,
+    'deriv_api.subscription_manager.SubscriptionManager.create_new_source': False,
+    'deriv_api.subscription_manager.SubscriptionManager.get_source': False,
+    'deriv_api.subscription_manager.SubscriptionManager.remove_key_on_error': False,
+    'deriv_api.subscription_manager.SubscriptionManager.save_subs_id': False,
+    'deriv_api.subscription_manager.SubscriptionManager.save_subs_per_msg_type': False,
+    'deriv_api.subscription_manager.SubscriptionManager.source_exists': False,
+    'deriv_api.subscription_manager.SubscriptionManager.forget': False,
+    'deriv_api.subscription_manager.SubscriptionManager.forget_all': False,
+    'deriv_api.subscription_manager.get_msg_type': False
+}
+
+
+class SubscriptionManager:
+    """
+        Subscription Manager - manage subscription channels
+
+        Makes sure there is always only one subscription channel for all requests of subscriptions,
+        keeps a history of received values for the subscription of ticks and forgets channels that
+        do not have subscribers. It also ensures that subscriptions are revived after connection
+        drop/account changed.
+
+        Parameters
+        ----------
+            api : deriv_api.DerivAPI
+
+        Example
+        -------
+        - create a new subscription for R_100
+        >>> source_tick_50: Observable  = await api.subscribe({'ticks': 'R_50'})
+        >>> subscription_id = 0
+        >>> def tick_50_callback(data):
+        >>>     global subscription_id
+        >>>     subscription_id = data['subscription']['id']
+        >>>     print(data)
+        >>> source_tick_50.subscribe(tick_50_callback)
+
+        - forget all ticks
+        >>> await api.forget_all('ticks')
+
+        - forget based on subscription id
+        >>> await api.forget(subscription_id)
+        """
+
+    def __init__(self, api):
+        self.api = api
+        self.sources: dict = {}
+        self.orig_sources: dict = {}
+        self.subs_id_to_key: dict = {}
+        self.key_to_subs_id: dict = {}
+        self.buy_key_to_contract_id: dict = {}
+        self.subs_per_msg_type: dict = {}
+
+    async def subscribe(self, request: dict) -> Observable:
+        """
+        Subscribe to a given request, returns a stream of new responses,
+        Errors should be handled by the user of the stream
+
+        Example
+        -------
+        >>> ticks = api.subscribe({ 'ticks': 'R_100' })
+        >>> ticks.subscribe(call_back_function)
+
+        Parameters
+        ----------
+        request : dict
+            A request object acceptable by the API
+
+        Returns
+        -------
+            Observable
+                An RxPY SObservable
+        """
+        if not get_msg_type(request):
+            raise APIError('Subscription type is not found in deriv-api')
+
+        if self.source_exists(request):
+            return self.get_source(request)
+
+        new_request: dict = request.copy()
+        new_request['subscribe'] = 1
+        return await self.create_new_source(new_request)
+
+    def get_source(self, request: dict) -> Optional[Subject]:
+        """
+        To get the source from the source list stored in sources
+
+        Parameters
+        ----------
+        request : dict
+            Request object
+
+        Returns
+        -------
+            Returns source observable if exists, otherwise returns None
+        """
+        key: bytes = dict_to_cache_key(request)
+        if key in self.sources:
+            return self.sources[key]
+
+        # if we have a buy subscription reuse that for poc
+        for c in self.buy_key_to_contract_id.values():
+            if c['contract_id'] == request['contract_id']:
+                return self.sources[c['buy_key']]
+
+        return None
+
+    def source_exists(self, request: dict):
+        """
+        Get the source by request
+
+        Parameters
+        ----------
+        request : dict
+            A request object
+
+        Returns
+        -------
+            Returns source observable if exists in source list, otherwise None
+
+        """
+        return self.get_source(request)
+
+    async def create_new_source(self, request: dict) -> Observable:
+        """
+        Create new source observable, stores it in source list and returns
+
+        Parameters
+        ----------
+        request : dict
+            A request object
+
+        Returns
+        -------
+            Returns source observable
+        """
+        key: bytes = dict_to_cache_key(request)
+
+        def forget_old_source() -> None:
+            if key not in self.key_to_subs_id:
+                return
+            # noinspection PyBroadException
+            try:
+                self.api.add_task(self.forget(self.key_to_subs_id[key]), 'forget old subscription')
+            except Exception as err:
+                self.api.sanity_errors.on_next(err)
+            return
+
+        self.orig_sources[key]: Observable = self.api.send_and_get_source(request)
+        source: Observable = self.orig_sources[key].pipe(
+            op.finally_action(forget_old_source),
+            op.share()
+        )
+        self.sources[key] = source
+        self.save_subs_per_msg_type(request, key)
+
+        async def process_response() -> None:
+            # noinspection PyBroadException
+            try:
+                response = await source.pipe(op.first(), op.to_future())
+                if request.get('buy'):
+                    self.buy_key_to_contract_id[key] = {
+                        'contract_id': response['buy']['contract_id'],
+                        'buy_key': key
+                    }
+                self.save_subs_id(key, response['subscription'])
+            except Exception:
+                self.remove_key_on_error(key)
+
+        self.api.add_task(process_response(), 'subs manager: process_response')
+        return source
+
+    async def forget(self, subs_id: str) -> dict:
+        """
+        Delete the source from source list, clears the subscription detail from subs_id_to_key and key_to_subs_id and
+        make api call to unsubscribe the subscription
+        Parameters
+        ----------
+        subs_id : str
+            Subscription id
+
+        Returns
+        -------
+            Returns dict - api response for forget call
+        """
+        self.complete_subs_by_ids(subs_id)
+        return await self.api.send({'forget': subs_id})
+
+    async def forget_all(self, *types) -> dict:
+        """
+        Unsubscribe all subscription's of given type. For each subscription, it deletes the source from source list,
+        clears the subscription detail from subs_id_to_key and key_to_subs_id. Make api call to unsubscribe all the
+        subscriptions of given types.
+        Parameters
+        ----------
+        types : Positional argument
+            subscription stream types example : ticks, candles
+
+        Returns
+        -------
+            Response from API call
+        """
+        # To include subscriptions that were automatically unsubscribed
+        # for example a proposal subscription is auto-unsubscribed after buy
+
+        for t in types:
+            for k in (self.subs_per_msg_type.get(t) or []):
+                self.complete_subs_by_key(k)
+            self.subs_per_msg_type[t] = []
+        return await self.api.send({'forget_all': list(types)})
+
+    def complete_subs_by_ids(self, *subs_ids):
+        """
+        Completes the subscription for the given subscription id's - delete the source from source list, clears the
+        subscription detail from subs_id_to_key and key_to_subs_id. Mark the original source as complete.
+
+        Parameters
+        ----------
+        subs_ids : Positional argument
+            subscription ids
+
+        """
+        for subs_id in subs_ids:
+            if subs_id in self.subs_id_to_key:
+                key = self.subs_id_to_key[subs_id]
+                self.complete_subs_by_key(key)
+
+    def save_subs_id(self, key: bytes, subscription: Union[dict, None]):
+        """
+        Saves the subscription detail in subs_id_to_key and key_to_subs_id
+
+        Parameters
+        ----------
+        key : bytes
+            API call request key. Key for key_to_subs_id
+        subscription : dict or None
+            subscription details - subscription id
+
+        """
+        if not subscription:
+            return self.complete_subs_by_key(key)
+
+        subs_id = subscription['id']
+        if subs_id not in self.subs_id_to_key:
+            self.subs_id_to_key[subs_id] = key
+            self.key_to_subs_id[key] = subs_id
+
+        return None
+
+    def save_subs_per_msg_type(self, request: dict, key: bytes):
+        """
+        Save the request's key in subscription per message type
+
+        Parameters
+        ----------
+        request : dict
+            API request object
+        key : bytes
+            API request key
+
+        """
+        msg_type = get_msg_type(request)
+        if msg_type:
+            self.subs_per_msg_type[msg_type] = self.subs_per_msg_type.get(msg_type) or []
+            self.subs_per_msg_type[msg_type].append(key)
+        else:
+            self.api.sanity_errors.next(APIError('Subscription type is not found in deriv-api'))
+
+    def remove_key_on_error(self, key: bytes):
+        """
+        Remove ths source from source list,  clears the subscription detail from subs_id_to_key and key_to_subs_id.
+        Mark the original source as complete.
+
+        Parameters
+        ----------
+        key : bytes
+            Request object key in bytes. Used to identify the subscription stored in key_to_subs_id
+
+        """
+        return lambda: self.complete_subs_by_key(key)
+
+    def complete_subs_by_key(self, key: bytes):
+        """
+        Identify the source from source list based on request object key and removes it. Clears the subscription detail
+        from subs_id_to_key and key_to_subs_id. Mark the original source as complete.
+
+        Parameters
+        ----------
+        key : bytes
+            Request object key to identify the subscription stored in key_to_subs_id
+
+        """
+        if not key or not self.sources[key]:
+            return
+
+        # Delete the source
+        del self.sources[key]
+        orig_source: Subject = self.orig_sources.pop(key)
+
+        try:
+            # Delete the subs id if exist
+            if key in self.key_to_subs_id:
+                subs_id = self.key_to_subs_id[key]
+                del self.subs_id_to_key[subs_id]
+                # Delete the key
+                del self.key_to_subs_id[key]
+
+            # Delete the buy key to contract_id mapping
+            del self.buy_key_to_contract_id[key]
+        except KeyError:
+            pass
+
+        # Mark the source complete
+        orig_source.on_completed()
+        orig_source.dispose()
+
+
+def get_msg_type(request: dict) -> str:
+    """
+    Get message type by request
+
+    Parameters
+    ----------
+    request : dict
+        Request
+
+    Returns
+    -------
+        Returns the next item from the iterator
+    """
+    return next((x for x in streams_list if x in request), None)
diff --git a/deriv_api/utils.py b/deriv_api/utils.py
new file mode 100644
index 0000000..c34d2c3
--- /dev/null
+++ b/deriv_api/utils.py
@@ -0,0 +1,33 @@
+import pickle
+import re
+
+
+def dict_to_cache_key(obj: dict) -> bytes:
+    """convert the dictionary object to Pickled representation of object as bytes
+
+    Parameter
+    ---------
+    obj : dict
+        Request arguments
+    Returns
+    ------- 
+        bytes
+            Pickled representation of request object as bytes
+    """
+
+    cloned_obj: dict = obj.copy()
+    for key in ['req_id', 'passthrough', 'subscribe']:
+        cloned_obj.pop(key, None)
+
+    return pickle.dumps(cloned_obj)
+
+
+def is_valid_url(url: str) -> bool:
+    regex = re.compile(
+        r'^wss?://'  # ws:// or wss://
+        r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'  # domain...
+        r'localhost|'  # localhost...
+        r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # ...or ip
+        r'(?::\d+)?'  # optional port
+        r'(?:/?|[/?]\S+)$', re.IGNORECASE)
+    return re.match(regex, url) is not None
diff --git a/docs/templates/config.mako b/docs/templates/config.mako
new file mode 100644
index 0000000..a26bf20
--- /dev/null
+++ b/docs/templates/config.mako
@@ -0,0 +1,61 @@
+<%!
+    # Template configuration. Copy over in your template directory
+    # (used with `--template-dir`) and adapt as necessary.
+    # Note, defaults are loaded from this distribution file, so your
+    # config.mako only needs to contain values you want overridden.
+    # You can also run pdoc with `--config KEY=VALUE` to override
+    # individual values.
+
+    html_lang = 'en'
+    show_inherited_members = False
+    extract_module_toc_into_sidebar = True
+    list_class_variables_in_index = False
+    sort_identifiers = False
+    show_type_annotations = False
+
+    # Show collapsed source code block next to each item.
+    # Disabling this can improve rendering speed of large modules.
+    show_source_code = False
+
+    # If set, format links to objects in online source code repository
+    # according to this template. Supported keywords for interpolation
+    # are: commit, path, start_line, end_line.
+    #git_link_template = 'https://github.com/USER/python-derive-api/blob/{commit}/{path}#L{start_line}-L{end_line}'  
+    git_link_template = None
+
+    # A prefix to use for every HTML hyperlink in the generated documentation.
+    # No prefix results in all links being relative.
+    link_prefix = ''
+
+    # Enable syntax highlighting for code/source blocks by including Highlight.js
+    syntax_highlighting = True
+
+    # Set the style keyword such as 'atom-one-light' or 'github-gist'
+    #     Options: https://github.com/highlightjs/highlight.js/tree/master/src/styles
+    #     Demo: https://highlightjs.org/static/demo/
+    hljs_style = 'github'
+
+    # If set, insert Google Analytics tracking code. Value is GA
+    # tracking id (UA-XXXXXX-Y).
+    google_analytics = ''
+
+    # If set, insert Google Custom Search search bar widget above the sidebar index.
+    # The whitespace-separated tokens represent arbitrary extra queries (at least one
+    # must match) passed to regular Google search. Example:
+    #google_search_query = 'inurl:github.com/USER/PROJECT  site:PROJECT.github.io  site:PROJECT.website'
+    google_search_query = ''
+
+    # Enable offline search using Lunr.js. For explanation of 'fuzziness' parameter, which is
+    # added to every query word, see: https://lunrjs.com/guides/searching.html#fuzzy-matches
+    # If 'index_docstrings' is False, a shorter index is built, indexing only
+    # the full object reference names.
+    #lunr_search = {'fuzziness': 1, 'index_docstrings': True}
+    lunr_search = None
+
+    # If set, render LaTeX math syntax within \(...\) (inline equations),
+    # or within \[...\] or $$...$$ or `.. math::` (block equations)
+    # as nicely-formatted math formulas using MathJax.
+    # Note: in Python docstrings, either all backslashes need to be escaped (\\)
+    # or you need to use raw r-strings.
+    latex_math = False
+%>
diff --git a/docs/templates/html.mako b/docs/templates/html.mako
new file mode 100644
index 0000000..4677e01
--- /dev/null
+++ b/docs/templates/html.mako
@@ -0,0 +1,451 @@
+<%
+  import os
+
+  import pdoc
+  from pdoc.html_helpers import extract_toc, glimpse, to_html as _to_html, format_git_link
+
+
+  def link(dobj: pdoc.Doc, name=None):
+    name = name or dobj.qualname + ('()' if isinstance(dobj, pdoc.Function) else '')
+    if isinstance(dobj, pdoc.External) and not external_links:
+        return name
+    url = dobj.url(relative_to=module, link_prefix=link_prefix,
+                   top_ancestor=not show_inherited_members)
+    return f'<a title="{dobj.refname}" href="{url}">{name}</a>'
+
+
+  def to_html(text):
+    return _to_html(text, docformat=docformat, module=module, link=link, latex_math=latex_math)
+
+
+  def get_annotation(bound_method, sep=':'):
+    annot = show_type_annotations and bound_method(link=link) or ''
+    if annot:
+        annot = ' ' + sep + '\N{NBSP}' + annot
+    return annot
+%>
+
+<%def name="ident(name)"><span class="ident">${name}</span></%def>
+
+<%def name="show_source(d)">
+  % if (show_source_code or git_link_template) and d.source and d.obj is not getattr(d.inherits, 'obj', None):
+    <% git_link = format_git_link(git_link_template, d) %>
+    % if show_source_code:
+      <details class="source">
+        <summary>
+            <span>Expand source code</span>
+            % if git_link:
+              <a href="${git_link}" class="git-link">Browse git</a>
+            %endif
+        </summary>
+        <pre><code class="python">${d.source | h}</code></pre>
+      </details>
+    % elif git_link:
+      <div class="git-link-div"><a href="${git_link}" class="git-link">Browse git</a></div>
+    %endif
+  %endif
+</%def>
+
+<%def name="show_desc(d, short=False)">
+  <%
+  inherits = ' inherited' if d.inherits else ''
+  docstring = glimpse(d.docstring) if short or inherits else d.docstring
+  %>
+  % if d.inherits:
+      <p class="inheritance">
+          <em>Inherited from:</em>
+          % if hasattr(d.inherits, 'cls'):
+              <code>${link(d.inherits.cls)}</code>.<code>${link(d.inherits, d.name)}</code>
+          % else:
+              <code>${link(d.inherits)}</code>
+          % endif
+      </p>
+  % endif
+  <div class="desc${inherits}">${docstring | to_html}</div>
+  % if not isinstance(d, pdoc.Module):
+  ${show_source(d)}
+  % endif
+</%def>
+
+<%def name="show_module_list(modules)">
+<h1>Python module list</h1>
+
+% if not modules:
+  <p>No modules found.</p>
+% else:
+  <dl id="http-server-module-list">
+  % for name, desc in modules:
+      <div class="flex">
+      <dt><a href="${link_prefix}${name}">${name}</a></dt>
+      <dd>${desc | glimpse, to_html}</dd>
+      </div>
+  % endfor
+  </dl>
+% endif
+</%def>
+
+<%def name="show_column_list(items)">
+  <%
+      two_column = len(items) >= 6 and all(len(i.name) < 20 for i in items)
+  %>
+  <ul class="${'two-column' if two_column else ''}">
+  % for item in items:
+    <li><code>${link(item, item.name)}</code></li>
+  % endfor
+  </ul>
+</%def>
+
+<%def name="show_module(module)">
+  <%
+  variables = module.variables(sort=sort_identifiers)
+  classes = module.classes(sort=sort_identifiers)
+  functions = module.functions(sort=sort_identifiers)
+  submodules = module.submodules()
+  %>
+
+  <%def name="show_func(f)">
+    <dt id="${f.refname}"><code class="name flex">
+        <%
+            params = ', '.join(f.params(annotate=show_type_annotations, link=link))
+            return_type = get_annotation(f.return_annotation, '\N{non-breaking hyphen}>')
+        %>
+        <span>${f.funcdef()} ${ident(f.name)}</span>(<span>${params})${return_type}</span>
+    </code></dt>
+    <dd>${show_desc(f)}</dd>
+  </%def>
+
+  <header>
+  % if http_server:
+    <nav class="http-server-breadcrumbs">
+      <a href="/">All packages</a>
+      <% parts = module.name.split('.')[:-1] %>
+      % for i, m in enumerate(parts):
+        <% parent = '.'.join(parts[:i+1]) %>
+        :: <a href="/${parent.replace('.', '/')}/">${parent}</a>
+      % endfor
+    </nav>
+  % endif
+  <h1 class="title">${'Namespace' if module.is_namespace else  \
+                      'Package' if module.is_package and not module.supermodule else \
+                      'Module'} <code>${module.name}</code></h1>
+  </header>
+
+  <section id="section-intro">
+  ${module.docstring | to_html}
+  ${show_source(module)}
+  </section>
+
+  <section>
+    % if submodules:
+    <h2 class="section-title" id="header-submodules">Sub-modules</h2>
+    <dl>
+    % for m in submodules:
+      <dt><code class="name">${link(m)}</code></dt>
+      <dd>${show_desc(m, short=True)}</dd>
+    % endfor
+    </dl>
+    % endif
+  </section>
+
+  <section>
+    % if variables:
+    <h2 class="section-title" id="header-variables">Global variables</h2>
+    <dl>
+    % for v in variables:
+      <% return_type = get_annotation(v.type_annotation) %>
+      <dt id="${v.refname}"><code class="name">var ${ident(v.name)}${return_type}</code></dt>
+      <dd>${show_desc(v)}</dd>
+    % endfor
+    </dl>
+    % endif
+  </section>
+
+  <section>
+    % if functions:
+    <h2 class="section-title" id="header-functions">Functions</h2>
+    <dl>
+    % for f in functions:
+      ${show_func(f)}
+    % endfor
+    </dl>
+    % endif
+  </section>
+
+  <section>
+    % if classes:
+    <h2 class="section-title" id="header-classes">Classes</h2>
+    <dl>
+    % for c in classes:
+      <%
+      class_vars = c.class_variables(show_inherited_members, sort=sort_identifiers)
+      smethods = c.functions(show_inherited_members, sort=sort_identifiers)
+      inst_vars = c.instance_variables(show_inherited_members, sort=sort_identifiers)
+      methods = c.methods(show_inherited_members, sort=sort_identifiers)
+      mro = c.mro()
+      subclasses = c.subclasses()
+      params = ', '.join(c.params(annotate=show_type_annotations, link=link))
+      %>
+      <dt id="${c.refname}"><code class="flex name class">
+          <span>class ${ident(c.name)}</span>
+          % if params:
+              <span>(</span><span>${params})</span>
+          % endif
+      </code></dt>
+
+      <dd>${show_desc(c)}
+
+      % if mro:
+          <h3>Ancestors</h3>
+          <ul class="hlist">
+          % for cls in mro:
+              <li>${link(cls)}</li>
+          % endfor
+          </ul>
+      %endif
+
+      % if subclasses:
+          <h3>Subclasses</h3>
+          <ul class="hlist">
+          % for sub in subclasses:
+              <li>${link(sub)}</li>
+          % endfor
+          </ul>
+      % endif
+      % if class_vars:
+          <h3>Class variables</h3>
+          <dl>
+          % for v in class_vars:
+              <% return_type = get_annotation(v.type_annotation) %>
+              <dt id="${v.refname}"><code class="name">var ${ident(v.name)}${return_type}</code></dt>
+              <dd>${show_desc(v)}</dd>
+          % endfor
+          </dl>
+      % endif
+      % if smethods:
+          <h3>Static methods</h3>
+          <dl>
+          % for f in smethods:
+              ${show_func(f)}
+          % endfor
+          </dl>
+      % endif
+      % if inst_vars:
+          <h3>Instance variables</h3>
+          <dl>
+          % for v in inst_vars:
+              <% return_type = get_annotation(v.type_annotation) %>
+              <dt id="${v.refname}"><code class="name">var ${ident(v.name)}${return_type}</code></dt>
+              <dd>${show_desc(v)}</dd>
+          % endfor
+          </dl>
+      % endif
+      % if methods:
+          <h3>Methods</h3>
+          <dl>
+          % for f in methods:
+              ${show_func(f)}
+          % endfor
+          </dl>
+      % endif
+
+      % if not show_inherited_members:
+          <%
+              members = c.inherited_members()
+          %>
+          % if members:
+              <h3>Inherited members</h3>
+              <ul class="hlist">
+              % for cls, mems in members:
+                  <li><code><b>${link(cls)}</b></code>:
+                      <ul class="hlist">
+                          % for m in mems:
+                              <li><code>${link(m, name=m.name)}</code></li>
+                          % endfor
+                      </ul>
+
+                  </li>
+              % endfor
+              </ul>
+          % endif
+      % endif
+
+      </dd>
+    % endfor
+    </dl>
+    % endif
+  </section>
+</%def>
+
+<%def name="module_index(module)">
+  <%
+  variables = module.variables(sort=sort_identifiers)
+  classes = module.classes(sort=sort_identifiers)
+  functions = module.functions(sort=sort_identifiers)
+  submodules = module.submodules()
+  supermodule = module.supermodule
+  %>
+  <nav id="sidebar">
+
+    <%include file="logo.mako"/>
+
+    % if google_search_query:
+        <div class="gcse-search" style="height: 70px"
+             data-as_oq="${' '.join(google_search_query.strip().split()) | h }"
+             data-gaCategoryParameter="${module.refname | h}">
+        </div>
+    % endif
+
+    % if lunr_search is not None:
+      <%include file="_lunr_search.inc.mako"/>
+    % endif
+
+    <h1>Index</h1>
+    ${extract_toc(module.docstring) if extract_module_toc_into_sidebar else ''}
+    <ul id="index">
+    % if supermodule:
+    <li><h3>Super-module</h3>
+      <ul>
+        <li><code>${link(supermodule)}</code></li>
+      </ul>
+    </li>
+    % endif
+
+    % if submodules:
+    <li><h3><a href="#header-submodules">Sub-modules</a></h3>
+      <ul>
+      % for m in submodules:
+        <li><code>${link(m)}</code></li>
+      % endfor
+      </ul>
+    </li>
+    % endif
+
+    % if variables:
+    <li><h3><a href="#header-variables">Global variables</a></h3>
+      ${show_column_list(variables)}
+    </li>
+    % endif
+
+    % if functions:
+    <li><h3><a href="#header-functions">Functions</a></h3>
+      ${show_column_list(functions)}
+    </li>
+    % endif
+
+    % if classes:
+    <li><h3><a href="#header-classes">Classes</a></h3>
+      <ul>
+      % for c in classes:
+        <li>
+        <h4><code>${link(c)}</code></h4>
+        <%
+            members = c.functions(sort=sort_identifiers) + c.methods(sort=sort_identifiers)
+            if list_class_variables_in_index:
+                members += (c.instance_variables(sort=sort_identifiers) +
+                            c.class_variables(sort=sort_identifiers))
+            if not show_inherited_members:
+                members = [i for i in members if not i.inherits]
+            if sort_identifiers:
+              members = sorted(members)
+        %>
+        % if members:
+          ${show_column_list(members)}
+        % endif
+        </li>
+      % endfor
+      </ul>
+    </li>
+    % endif
+
+    </ul>
+  </nav>
+</%def>
+
+<!doctype html>
+<html lang="${html_lang}">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
+  <meta name="generator" content="pdoc ${pdoc.__version__}" />
+
+<%
+    module_list = 'modules' in context.keys()  # Whether we're showing module list in server mode
+%>
+
+  % if module_list:
+    <title>Python module list</title>
+    <meta name="description" content="A list of documented Python modules." />
+  % else:
+    <title>${module.name} API documentation</title>
+    <meta name="description" content="${module.docstring | glimpse, trim, h}" />
+  % endif
+
+  <link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
+  <link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
+  % if syntax_highlighting:
+    <link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/${hljs_style}.min.css" crossorigin>
+  %endif
+
+  <%namespace name="css" file="css.mako" />
+  <style>${css.mobile()}</style>
+  <style media="screen and (min-width: 700px)">${css.desktop()}</style>
+  <style media="print">${css.print()}</style>
+
+  % if google_analytics:
+    <script>
+    window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
+    ga('create', '${google_analytics}', 'auto'); ga('send', 'pageview');
+    </script><script async src='https://www.google-analytics.com/analytics.js'></script>
+  % endif
+
+  % if google_search_query:
+    <link rel="preconnect" href="https://www.google.com">
+    <script async src="https://cse.google.com/cse.js?cx=017837193012385208679:pey8ky8gdqw"></script>
+    <style>
+        .gsc-control-cse {padding:0 !important;margin-top:1em}
+        body.gsc-overflow-hidden #sidebar {overflow: visible;}
+    </style>
+  % endif
+
+  % if latex_math:
+    <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-AMS_CHTML" integrity="sha256-kZafAc6mZvK3W3v1pHOcUix30OHQN6pU/NO2oFkqZVw=" crossorigin></script>
+  % endif
+
+  % if syntax_highlighting:
+    <script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
+    <script>window.addEventListener('DOMContentLoaded', () => {hljs.configure({languages:[]}); hljs.initHighlighting(); })</script>
+  % endif
+
+  <%include file="head.mako"/>
+</head>
+<body>
+<main>
+  % if module_list:
+    <article id="content">
+      ${show_module_list(modules)}
+    </article>
+  % else:
+    <article id="content">
+      ${show_module(module)}
+    </article>
+    ${module_index(module)}
+  % endif
+</main>
+
+<footer id="footer">
+    <%include file="credits.mako"/>
+    <p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> ${pdoc.__version__}</a>.</p>
+</footer>
+
+% if http_server and module:  ## Auto-reload on file change in dev mode
+    <script>
+    setInterval(() =>
+        fetch(window.location.href, {
+            method: "HEAD",
+            cache: "no-store",
+            headers: {"If-None-Match": "${os.stat(module.obj.__file__).st_mtime}"},
+        }).then(response => response.ok && window.location.reload()), 700);
+    </script>
+% endif
+</body>
+</html>
\ No newline at end of file
diff --git a/docs/usage_examples.md b/docs/usage_examples.md
new file mode 100644
index 0000000..1defc6f
--- /dev/null
+++ b/docs/usage_examples.md
@@ -0,0 +1,160 @@
+
+# Usage examples
+## Short examples
+
+```python
+    from deriv_api import DerivAPI
+    api = DerivAPI(app_id=app_id)
+```
+
+### Authenticate to an account using token
+```python
+    authorize = await api.authorize(api_token)
+    print(authorize)
+```
+### Get Balance
+```python
+    account = await api.balance()
+    print(account) 
+```
+### Get all the assets info
+```python
+    assets = await api.asset_index({"asset_index": 1})
+    print(assets)
+```
+
+To get assets info from cache
+```python
+    assets = await api.cache.asset_index({"asset_index": 1})
+    print(assets)
+```
+
+### Get all active symbols
+```python
+    active_symbols = await api.active_symbols({"active_symbols": "full"})
+    print(active_symbols)
+```
+
+To get active symbols from cache
+```python
+    active_symbols = await api.cache.active_symbols({"active_symbols": "full"})
+    print(active_symbols)
+```
+
+### Get proposal
+```python
+    proposal = await api.proposal({"proposal": 1, "amount": 100, "barrier": "+0.1", "basis": "payout",
+                                   "contract_type": "CALL", "currency": "USD", "duration": 60, "duration_unit": "s",
+                                   "symbol": "R_100"
+    })
+    print(proposal) 
+```
+
+subscribe the proposal stream
+```python
+    source_proposal: Observable = await api.subscribe({"proposal": 1, "amount": 100, "barrier": "+0.1", "basis": "payout",
+                                           "contract_type": "CALL", "currency": "USD", "duration": 160,
+                                           "duration_unit": "s",
+                                           "symbol": "R_100",
+                                           "subscribe": 1
+                                           })
+    source_proposal.subscribe(lambda proposal: print(proposal))
+```
+
+### Buy
+```python
+    proposal_id = proposal.get('proposal').get('id')
+    buy = await api.buy({"buy": proposal_id, "price": 100})
+    print(buy)
+```
+
+### open contract detail
+```python
+    contract_id = buy.get('buy').get('contract_id')
+    poc = await api.proposal_open_contract(
+        {"proposal_open_contract": 1, "contract_id": contract_id })
+    print(poc)
+```
+
+subscribe the open contract stream
+```
+    source_poc: Observable = await api.subscribe({"proposal_open_contract": 1, "contract_id": contract_id})
+    source_poc.subscribe(lambda poc: print(poc)
+```
+
+### Sell 
+```python
+    contract_id = buy.get('buy').get('contract_id')
+    sell = await api.sell({"sell": contract_id, "price": 40})
+    print(sell)
+```
+
+### Profit table
+```python
+    profit_table = await api.profit_table({"profit_table": 1, "description": 1, "sort": "ASC"})
+    print(profit_table)
+```
+
+### Transaction statement
+```python
+    statement = await api.statement({"statement": 1, "description": 1, "limit": 100, "offset": 25})
+    print(statement)
+```
+
+### Subscribe a stream
+
+We are using rxpy to maintain our deriv api subscriptions. Please distinguish api subscription from rxpy sequence subscription
+```python
+    # creating a rxpy sequence object to represent deriv api streams
+    source_tick_50 = await api.subscribe({'ticks': 'R_50'})
+
+    # subscribe the rxpy sequence with a callback function,
+    # when the data received, the call back function will be called
+    source_tick_50.subscribe(lambda tick: print(tick))
+```
+
+### unsubscribe the rxpy sequence
+```python
+    seq_sub = source_tick_50.subscribe(lambda tick: print(tick))
+    seq_sub.dispose()
+```
+
+### unsubscribe the deriv api stream
+
+There are 2 ways to unsubscribe deriv api stream
+
+- by `dispose` all sequence subscriptions
+```python
+    # creating a rxpy sequence object to represent deriv api streams
+    source_tick_50 = await api.subscribe({'ticks': 'R_50'})
+    # subscribe the rxpy sequence with a callback function,
+    # when the data received , the call back function will be called
+    seq_sub1 = source_tick_50.subscribe(lambda tick: print(f"get tick from sub1 {tick}"))
+    seq_sub2 = source_tick_50.subscribe(lambda tick: print(f"get tick from sub2 {tick}"))
+    seq_sub1.dispose()
+    seq_sub2.dispose()
+    # When all seq subscriptions of one sequence are disposed. Then a `forget` will be called and that deriv api stream will be unsubscribed
+```
+
+
+- by `forget` that deriv stream
+```python
+    # get a datum first
+    from rx import operators as op
+    tick = await source_tick_50.pipe(op.first(), op.to_future)
+    api.forget(tick['R_50']['subscription']['id'])
+```
+
+### print errors
+```python
+    api.sanity_errors.subscribe(lambda err: print(err))
+```
+
+### do something when one type of message coming
+```python
+    async def print_hello_after_authorize():
+        auth_data = await api.expect_response('authorize')
+        print(f"Hello {auth_data['authorize']['fullname']}")
+    asyncio.create_task(print_hello_after_authorize())
+    api.authorize({'authorize': 'AVALIDTOKEN'})
+```
diff --git a/examples/simple_bot1.py b/examples/simple_bot1.py
new file mode 100644
index 0000000..7772e78
--- /dev/null
+++ b/examples/simple_bot1.py
@@ -0,0 +1,85 @@
+# run it like PYTHONPATH=. python3 examples/simple_bot1.py
+import sys
+import asyncio
+import os
+from deriv_api import DerivAPI
+from deriv_api import APIError
+
+app_id = 1089
+api_token = os.getenv('DERIV_TOKEN', '')
+
+if len(api_token) == 0:
+    sys.exit("DERIV_TOKEN environment variable is not set")
+
+
+async def sample_calls():
+    api = DerivAPI(app_id=app_id)
+
+    response = await api.ping({'ping': 1})
+    if response['ping']:
+        print(response['ping'])
+
+    active_symbols = await api.active_symbols({"active_symbols": "brief", "product_type": "basic"})
+    print(active_symbols)
+
+    # Authorize
+    authorize = await api.authorize(api_token)
+    print(authorize)
+
+    # Get Balance
+    response = await api.balance()
+    response = response['balance']
+    currency = response['currency']
+    print("Your current balance is", response['currency'], response['balance'])
+
+    # Get active symbols from cache
+    cached_active_symbols = await api.cache.active_symbols({"active_symbols": "brief", "product_type": "basic"})
+    print(cached_active_symbols)
+
+    # Get assets
+    assets = await api.cache.asset_index({"asset_index": 1})
+    print(assets)
+
+    # Get proposal
+    proposal = await api.proposal({"proposal": 1, "amount": 100, "barrier": "+0.1", "basis": "payout",
+                                   "contract_type": "CALL", "currency": "USD", "duration": 60, "duration_unit": "s",
+                                   "symbol": "R_100"
+                                   })
+    print(proposal)
+
+    # Buy
+    response = await api.buy({"buy": proposal.get('proposal').get('id'), "price": 100})
+    print(response)
+    print(response.get('buy').get('buy_price'))
+    print(response.get('buy').get('contract_id'))
+    print(response.get('buy').get('longcode'))
+    await asyncio.sleep(1) # wait 1 second
+    print("after buy")
+
+    # open contracts
+    poc = await api.proposal_open_contract(
+        {"proposal_open_contract": 1, "contract_id": response.get('buy').get('contract_id')})
+    print(poc)
+    print("waiting is sold........................")
+    if not poc.get('proposal_open_contract').get('is_sold'):
+        # sell
+        try:
+            await asyncio.sleep(1) # wainting for 1 second for entry tick
+            sell = await api.sell({"sell": response.get('buy').get('contract_id'), "price": 40})
+            print(sell)
+        except APIError as err:
+            print("error!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+            print(err)
+
+    # profit table
+    profit_table = await api.profit_table({"profit_table": 1, "description": 1, "sort": "ASC"})
+    print(profit_table)
+
+    # transaction statement
+    statement = await api.statement({"statement": 1, "description": 1, "limit": 100, "offset": 25})
+    print(statement)
+    print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!end!!!!!!!!!!!!!!!!!!!!1")
+    await api.clear()
+
+
+asyncio.run(sample_calls())
diff --git a/examples/simple_bot2.py b/examples/simple_bot2.py
new file mode 100644
index 0000000..a46e687
--- /dev/null
+++ b/examples/simple_bot2.py
@@ -0,0 +1,55 @@
+import sys
+sys.path.append('.')
+import asyncio
+import os
+from deriv_api import DerivAPI
+import websockets
+
+
+app_id = 1089
+api_token = os.getenv('DERIV_TOKEN', '')
+
+if len(api_token) == 0:
+    sys.exit("DERIV_TOKEN environment variable is not set")
+
+async def connect():
+    url = f'wss://frontend.binaryws.com/websockets/v3?l=EN&app_id={app_id}'
+    connection = await websockets.connect(url)
+
+    return connection
+
+async def sample_calls():
+    # create your own websocket connection and pass it as argument to DerivAPI
+    connection = await connect()
+    api = DerivAPI(connection=connection)
+
+    response = await api.ping({'ping':1})
+    if response['ping']:
+        print(response['ping'])
+
+    """ 
+    To test deriv_api try reconnect automatically on disconnecting, uncomment the below comment then run this script
+    disconnect your network & reconnect back. Enable the loglevel to logging.DEBUG to see the ping/pong 
+    """
+    # await asyncio.sleep(300)
+
+    # Authorize
+    authorize = await api.authorize(api_token)
+    print(authorize)
+
+    # Get Balance
+    response = await api.balance()
+    response = response['balance']
+    currency = response['currency']
+    print("Your current balance is", response['currency'], response['balance'])
+
+    # Get active symbols from cache
+    cached_active_symbols = await api.cache.active_symbols({"active_symbols": "brief", "product_type": "basic"})
+    print(cached_active_symbols)
+
+    # Get assets
+    assets = await api.cache.asset_index({"asset_index": 1})
+    print(assets)
+    await api.clear()
+
+asyncio.run(sample_calls())
diff --git a/examples/simple_bot3.py b/examples/simple_bot3.py
new file mode 100644
index 0000000..672848a
--- /dev/null
+++ b/examples/simple_bot3.py
@@ -0,0 +1,40 @@
+# run it like PYTHONPATH=. python3 examples/simple_bot1.py
+import asyncio
+from deriv_api import DerivAPI
+from rx import Observable
+app_id = 1089
+
+
+async def sample_calls():
+    api = DerivAPI(app_id=app_id)
+    wait_tick = api.expect_response('tick')
+    last_data = {}
+    source_tick_50: Observable  = await api.subscribe({'ticks': 'R_50'})
+    def create_subs_cb(symbol):
+        count = 1
+        def cb(data):
+            nonlocal count
+            count = count + 1
+            last_data[symbol] = data
+            print(f"get symbol {symbol} {count}")
+        return cb
+
+    a_sub = source_tick_50.subscribe(create_subs_cb('R_50'))
+    b_sub = source_tick_50.subscribe(create_subs_cb('R_50'))
+    source_tick_100: Observable  = await api.subscribe({'ticks': 'R_100'})
+    source_tick_100.subscribe(create_subs_cb('R_100'))
+    first_tick = await wait_tick
+    print(f"first tick is {first_tick}")
+    await asyncio.sleep(5)
+    print("now will forget")
+    #await api.forget(last_data['R_50']['subscription']['id'])
+    #await api.forget(last_data['R_100']['subscription']['id'])
+    #await api.forget_all('ticks')
+    a_sub.dispose()
+    await asyncio.sleep(5)
+    print("disposing the last one will call forget")
+    b_sub.dispose()
+    await asyncio.sleep(5)
+    await api.clear()
+
+asyncio.run(sample_calls())
diff --git a/examples/simple_bot4.py b/examples/simple_bot4.py
new file mode 100644
index 0000000..8170f5b
--- /dev/null
+++ b/examples/simple_bot4.py
@@ -0,0 +1,53 @@
+# run it like PYTHONPATH=. python3 examples/simple_bot4.py
+import sys
+import asyncio
+import os
+from deriv_api import DerivAPI
+from rx import Observable
+
+app_id = 1089
+api_token = os.getenv('DERIV_TOKEN', '')
+expected_payout = os.getenv('EXPECTED_PAYOUT', 10)
+
+if len(api_token) == 0:
+    sys.exit("DERIV_TOKEN environment variable is not set")
+
+
+async def sample_calls():
+    api = DerivAPI(app_id=app_id)
+
+    # Authorize
+    authorize = await api.authorize(api_token)
+
+    asyncio.create_task(buy_proposal(api))
+
+    # Subscribe proposal
+    source_proposal: Observable = await api.subscribe({"proposal": 1, "amount": 10, "barrier": "+0.1",
+                                                       "basis": "payout",
+                                                       "contract_type": "CALL", "currency": "USD", "duration": 160,
+                                                       "duration_unit": "s",
+                                                       "symbol": "R_100"
+                                                       })
+    source_proposal.subscribe()
+    await asyncio.sleep(5)
+
+
+# Buy contract
+async def buy_proposal(api):
+    proposal = await api.expect_response('proposal')
+    if proposal.get('proposal').get('payout') >= expected_payout:
+        proposal_id = proposal.get('proposal').get('id')
+        # buy contract
+        buy = await api.buy({"buy": proposal_id, "price": 10})
+        contract_id = buy.get('buy').get('contract_id')
+
+        # open contract stream
+        source_poc: Observable = await api.subscribe({"proposal_open_contract": 1, "contract_id": contract_id})
+        source_poc.subscribe(lambda poc: print(poc))
+        await asyncio.sleep(10)
+
+        await api.forget(proposal.get('subscription').get('id'))
+
+loop = asyncio.get_event_loop()
+asyncio.run(sample_calls())
+loop.run_forever()
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..b5a3c46
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,6 @@
+[build-system]
+requires = [
+    "setuptools>=42",
+    "wheel"
+]
+build-backend = "setuptools.build_meta"
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..afbcae6
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,27 @@
+#
+from setuptools import find_packages, setup
+setup(
+    name='python_deriv_api',
+    packages=find_packages(include=['deriv_api']),
+    version='0.1.0',
+    description='Python bindings for deriv.com websocket API',
+    author='Deriv Group Services Ltd',
+    author_email='learning+python@deriv.com',
+    license='MIT',
+    install_requires=['websockets==9.1', 'rx==3.1.1'],
+    test_suite='tests',
+    url='https://github.com/binary-com/python-deriv-api',
+    project_urls={
+        'Bug Tracker': "https://github.com/binary-com/python-deriv-api/issues",
+        'Documentation': "https://binary-com.github.io/python-deriv-api",
+        'Source Code': "https://github.com/binary-com/python-deriv-api"
+
+    },
+    python_requires="==3.9.6",
+    classifiers=[
+        "Programming Language :: Python :: 3",
+        "Operating System :: OS Independent",
+    ],
+    long_description_content_type="text/markdown",
+    long_description=open('README.md').read()
+)


Test patch:
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..5ee6477
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+testpaths = tests
diff --git a/tests/test_cache.py b/tests/test_cache.py
new file mode 100644
index 0000000..0086b4a
--- /dev/null
+++ b/tests/test_cache.py
@@ -0,0 +1,21 @@
+from deriv_api.cache import Cache
+from deriv_api.errors import ConstructionError
+from deriv_api.in_memory import InMemory
+import pytest
+
+class Api:
+    def __init__(self):
+        self.seq = 0
+    async def send(self, request):
+        # seq will change every time send is called
+        self.seq = self.seq + 1
+        return {'request': request, 'seq': self.seq, 'msg_type': request['msg_type']}
+
+@pytest.mark.asyncio
+async def test_cache():
+    with pytest.raises(ConstructionError, match='Cache object needs an API to work'):
+        Cache(None, None)
+    api = Api();
+    cache = Cache(api, InMemory())
+    assert (await cache.send({'msg_type':"a message"})) == {'request': {'msg_type': 'a message'}, 'seq': 1, 'msg_type': 'a message'} , "api send is called first time"
+    assert (await cache.send({'msg_type':"a message"})) == {'request': {'msg_type': 'a message'}, 'seq': 1, 'msg_type': 'a message'} , "date fetched from cache second time"
diff --git a/tests/test_custom_future.py b/tests/test_custom_future.py
new file mode 100644
index 0000000..d1c269f
--- /dev/null
+++ b/tests/test_custom_future.py
@@ -0,0 +1,176 @@
+import asyncio
+
+import pytest
+from deriv_api.easy_future import EasyFuture
+from asyncio.exceptions import InvalidStateError, CancelledError
+import sys
+
+def test_custom_future():
+    f1 = EasyFuture()
+    assert f1.is_pending()
+    f1.resolve("hello")
+    assert f1.result() == "hello"
+    assert f1.is_resolved()
+    with pytest.raises(InvalidStateError, match="invalid state"):
+        f1.reject("world")
+    f2 = EasyFuture()
+    f2.reject(Exception)
+    assert f2.is_rejected()
+
+@pytest.mark.asyncio
+async def test_wrap():
+    # test resolved
+    f1 = asyncio.Future()
+    f2 = EasyFuture.wrap(f1)
+    assert isinstance(f2, EasyFuture)
+    assert f1.get_loop() is f2.get_loop()
+    assert f2.is_pending()
+    f1.set_result("hello")
+    await f2
+    assert f2.result() == "hello"
+    assert f2.done()
+
+    # test reject
+    f1 = asyncio.Future()
+    f2 = EasyFuture.wrap(f1)
+    f1.set_exception(Exception("hello"))
+    with pytest.raises(Exception, match='hello'):
+        await f2
+    assert f2.done()
+    assert f2.is_rejected()
+
+    # test upstream cancel
+    f1 = asyncio.Future()
+    f2 = EasyFuture.wrap(f1)
+    f1.cancel("hello")
+    with pytest.raises(CancelledError, match='hello'):
+        await f2
+    assert f2.done()
+    assert f2.is_cancelled()
+
+    # test downstream cancel
+    f1 = asyncio.Future()
+    f2 = EasyFuture.wrap(f1)
+    f2.cancel("hello")
+    with pytest.raises(CancelledError, match='hello'):
+        await f1
+    assert f1.done()
+    assert f1.cancelled()
+
+
+@pytest.mark.asyncio
+async def test_future_then():
+    # test upstream ok
+    # test callback future ok
+    f1 = EasyFuture()
+    def then_callback(last_result):
+        f = EasyFuture()
+        f.set_result(f"result: {last_result}")
+        return f
+    f2 = f1.then(then_callback)
+    f1.set_result("f1 ok")
+    assert (await f2) == 'result: f1 ok', "if inside future has result, then_future will has result too"
+
+    # test callback fail
+    f1 = EasyFuture()
+
+    def then_callback(last_result):
+        f = EasyFuture()
+        f.set_exception(Exception(f"result: {last_result}"))
+        return f
+
+    f2 = f1.then(then_callback)
+    f1.set_result("f1 ok")
+    with pytest.raises(Exception, match='result: f1 ok'):
+        await f2
+
+    # test upstream fail
+    # test inside future ok
+    f1 = EasyFuture()
+    result = None
+
+    def else_callback(last_exception: Exception):
+        f = EasyFuture()
+        f.set_result(f"f1 exception {last_exception.args[0]}")
+        return f
+
+    f2 = f1.catch(else_callback)
+    f1.set_exception(Exception("f1 bad"))
+    assert (await f2) == 'f1 exception f1 bad'
+
+    # test inside future exception
+    f1 = EasyFuture()
+    result = None
+
+    def else_callback(last_exception: Exception):
+        f = EasyFuture()
+        f.set_exception(Exception(f"f1 exception {last_exception.args[0]}"))
+        return f
+
+    f2 = f1.then(None, else_callback)
+    f1.set_exception(Exception("f1 bad"))
+    with pytest.raises(Exception, match='f1 exception f1 bad'):
+        await f2
+
+    # upstream cancelled
+    f1 = EasyFuture()
+
+    def else_callback(last_exception: Exception):
+        f = EasyFuture()
+        f.set_exception(Exception(f"f1 exception {last_exception.args[0]}"))
+        return f
+
+    f2 = f1.then(None, else_callback)
+    f1.cancel('f1 cancelled')
+    with pytest.raises(asyncio.exceptions.CancelledError, match='Upstream future cancelled'):
+        await f2
+
+    # callback future cancelled
+    f1 = EasyFuture()
+
+    def then_callback(result):
+        f = EasyFuture()
+        f.cancel(f"callback cancelled with f1 {result}")
+        return f
+
+    f2 = f1.then(then_callback)
+    f1.set_result('f1 ok')
+    with pytest.raises(asyncio.exceptions.CancelledError, match='callback cancelled with f1 f1 ok'):
+        await f2
+
+    # test no right call back
+    f1 = EasyFuture()
+
+    def else_callback(result):
+        f = EasyFuture()
+        f.cancel(f"f1 ok {result}")
+        return f
+
+    f2 = f1.then(None, else_callback)
+    f1.set_result('f1 ok')
+    assert (await f2) == 'f1 ok', 'If no suitable callback, then clone the result'
+
+def test_refcount():
+    # test then method
+    f1 = EasyFuture()
+    assert sys.getrefcount(f1) == 2, "new created future has 2 refcount"
+    def then_cb():
+        return EasyFuture().resolve(True)
+    def else_cb():
+        return EasyFuture().resolve(True)
+    f1.then(then_cb(), else_cb)
+    assert sys.getrefcount(f1) == 2, "after add then else db, future has 2 refcount"
+
+    #test cascade method
+    core_future = asyncio.get_event_loop().create_future()
+    assert sys.getrefcount(core_future) == 2, "new created future has 2 refcount"
+    custom_future = EasyFuture()
+    custom_future.cascade(core_future)
+    assert sys.getrefcount(core_future) == 2, "after cascade, core_future future has 2 refcount"
+    assert sys.getrefcount(custom_future) == 3, "after cascade, custom future has 3 refcount"
+
+    # test wrap method
+    core_future = asyncio.get_event_loop().create_future()
+    custom_future = EasyFuture.wrap(core_future)
+    assert sys.getrefcount(core_future) == 2, "after cascade, core_future future has 2 refcount"
+    assert sys.getrefcount(custom_future) == 3, "after cascade, custom future has 3 refcount"
diff --git a/tests/test_deriv_api.py b/tests/test_deriv_api.py
new file mode 100644
index 0000000..880b274
--- /dev/null
+++ b/tests/test_deriv_api.py
@@ -0,0 +1,429 @@
+import asyncio
+import sys
+import traceback
+import pytest
+import pytest_mock
+import rx
+
+import deriv_api
+from deriv_api.errors import APIError, ConstructionError, ResponseError
+from deriv_api.easy_future import EasyFuture
+from rx.subject import Subject
+import rx.operators as op
+import pickle
+import json
+from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError
+
+class MockedWs:
+    def __init__(self):
+        self.data = []
+        self.called = {'send': [], 'recv' : []}
+        self.slept_at = 0
+        self.queue = Subject()
+        self.req_res_map = {}
+        async def build_queue():
+            while 1:
+                await asyncio.sleep(0.01)
+                # make queue
+                for idx, d in enumerate(self.data):
+                    if d is None:
+                        continue
+                    await asyncio.sleep(0.01)
+                    try:
+                        self.queue.on_next(json.dumps(d))
+                    except Exception as err:
+                        print(str(err))
+                    # if subscription, then we keep it
+                    if not d.get('subscription'):
+                        self.data[idx] = None
+        self.task_build_queue = asyncio.create_task(build_queue())
+    async def send(self, request):
+        self.called['send'].append(request)
+        request = json.loads(request)
+        new_request = request.copy()
+        # req_id will be generated by api automatically
+        req_id = new_request.pop('req_id')
+        key = pickle.dumps(new_request)
+        response = self.req_res_map.get(key)
+        if response:
+            response['req_id'] = req_id
+            self.data.append(response)
+            self.req_res_map.pop(key)
+        forget_id = request.get('forget')
+        if forget_id:
+            found = 0
+            for idx, d in enumerate(self.data):
+                if d is None:
+                    continue
+                subscription_data = d.get('subscription')
+                if subscription_data and subscription_data['id'] == forget_id:
+                    self.data[idx] = None
+                    found = 1
+                    break
+            self.data.append({"echo_req": {
+                'req_id': req_id,
+                'forget': forget_id,
+            },
+                'forget': found,
+                'req_id': req_id,
+                'msg_type': 'forget'
+            })
+
+    async def recv(self):
+        self.called['recv'].append(None)
+        data = await self.queue.pipe(op.first(),op.to_future())
+        return data
+
+    def add_data(self,response):
+        request = response['echo_req'].copy()
+        # req_id will be added by api automatically
+        # we remove it here for consistence
+        request.pop('req_id', None)
+        key = pickle.dumps(request)
+        self.req_res_map[key] = response
+
+    def clear(self):
+        self.task_build_queue.cancel('end')
+
+def test_connect_parameter():
+    with pytest.raises(ConstructionError, match=r"An app_id is required to connect to the API"):
+        deriv_api_obj = deriv_api.DerivAPI(endpoint=5432)
+
+    with pytest.raises(ConstructionError, match=r"Endpoint must be a string, passed: <class 'int'>"):
+        deriv_api_obj = deriv_api.DerivAPI(app_id=1234, endpoint=5432)
+
+    with pytest.raises(ConstructionError, match=r"Invalid URL:local123host"):
+        deriv_api_obj = deriv_api.DerivAPI(app_id=1234, endpoint='local123host')
+
+@pytest.mark.asyncio
+async def test_deriv_api(mocker):
+    mocker.patch('deriv_api.DerivAPI.api_connect', return_value='')
+    api = deriv_api.DerivAPI(app_id=1234, endpoint='localhost')
+    assert(isinstance(api, deriv_api.DerivAPI))
+    await asyncio.sleep(0.1)
+    await api.clear()
+
+@pytest.mark.asyncio
+async def test_get_url(mocker):
+    api = get_deriv_api(mocker)
+    assert api.get_url("localhost") == "wss://localhost"
+    assert api.get_url("ws://localhost") == "ws://localhost"
+    with pytest.raises(ConstructionError, match=r"Invalid URL:testurl"):
+        api.get_url("testurl")
+    await asyncio.sleep(0.1)
+    await api.clear()
+
+def get_deriv_api(mocker):
+    mocker.patch('deriv_api.DerivAPI.api_connect', return_value=EasyFuture().set_result(1))
+    api = deriv_api.DerivAPI(app_id=1234, endpoint='localhost')
+    return api
+
+@pytest.mark.asyncio
+async def test_mocked_ws():
+    wsconnection = MockedWs()
+    data1 = {"echo_req":{"ticks" : 'R_50', 'req_id': 1} ,"msg_type": "ticks", "req_id": 1, "subscription": {"id": "world"}}
+    data2 = {"echo_req":{"ping": 1, 'req_id': 2},"msg_type": "ping", "pong": 1, "req_id": 2}
+    wsconnection.add_data(data1)
+    wsconnection.add_data(data2)
+    await wsconnection.send(json.dumps(data1["echo_req"]))
+    await wsconnection.send(json.dumps(data2["echo_req"]))
+    assert json.loads(await wsconnection.recv()) == data1, "we can get first data"
+    assert json.loads(await wsconnection.recv()) == data2, "we can get second data"
+    assert json.loads(await wsconnection.recv()) == data1, "we can still get first data becaues it is a subscription"
+    assert json.loads(await wsconnection.recv()) == data1, "we will not get second data because it is not a subscription"
+    assert len(wsconnection.called['send']) == 2
+    assert len(wsconnection.called['recv']) == 4
+    wsconnection.clear()
+
+@pytest.mark.asyncio
+async def test_simple_send():
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection = wsconnection)
+    data1 = {"echo_req":{"ping": 1},"msg_type": "ping", "pong": 1}
+    data2 = {"echo_req":{"ticks" : 'R_50'} ,"msg_type": "ticks"}
+    wsconnection.add_data(data1)
+    wsconnection.add_data(data2)
+    res1 = data1.copy()
+    add_req_id(res1, 1)
+    res2 = data2.copy()
+    add_req_id(res2, 2)
+    assert await api.send(data1['echo_req']) == res1
+    assert await api.ticks(data2['echo_req']) == res2
+    assert len(wsconnection.called['send']) == 2
+    wsconnection.clear()
+    await api.clear()
+
+@pytest.mark.asyncio
+async def test_subscription():
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    r50_data = {
+        'echo_req': {'ticks': 'R_50', 'subscribe': 1},
+        'msg_type': 'tick',
+        'subscription': {'id': 'A11111'}
+    }
+    r100_data = {
+        'echo_req': {'ticks': 'R_100', 'subscribe': 1},
+        'msg_type': 'tick',
+        'subscription': {'id': 'A22222'}
+    }
+    wsconnection.add_data(r50_data)
+    wsconnection.add_data(r100_data)
+    r50_req = r50_data['echo_req']
+    r50_req.pop('subscribe');
+    r100_req = r100_data['echo_req']
+    r100_req.pop('subscribe');
+    sub1 = await api.subscribe(r50_req)
+    sub2 = await api.subscribe(r100_req)
+    f1 = sub1.pipe(op.take(2), op.to_list(), op.to_future())
+    f2 = sub2.pipe(op.take(2), op.to_list(), op.to_future())
+    result = await asyncio.gather(f1, f2)
+    assert result == [[r50_data, r50_data], [r100_data, r100_data]]
+    await asyncio.sleep(0.01)  # wait sending 'forget' finished
+    assert wsconnection.called['send'] == [
+        '{"ticks": "R_50", "subscribe": 1, "req_id": 1}',
+        '{"ticks": "R_100", "subscribe": 1, "req_id": 2}',
+        '{"forget": "A11111", "req_id": 3}',
+        '{"forget": "A22222", "req_id": 4}']
+    wsconnection.clear()
+    await api.clear()
+
+@pytest.mark.asyncio
+async def test_forget():
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    # test subscription forget will mark source done
+    r50_data = {
+        'echo_req': {'ticks': 'R_50', 'subscribe': 1},
+        'msg_type': 'tick',
+        'subscription': {'id': 'A11111'}
+    }
+    wsconnection.add_data(r50_data)
+    r50_req = r50_data['echo_req']
+    r50_req.pop('subscribe');
+    sub1: rx.Observable = await api.subscribe(r50_req)
+    complete = False
+
+    def on_complete():
+        nonlocal complete
+        complete = True
+
+    sub1.subscribe(on_completed=on_complete)
+    await asyncio.sleep(0.1)
+    assert not complete, 'subscription not stopped'
+    await api.forget('A11111')
+    await asyncio.sleep(0.1)
+    assert complete, 'subscription stopped after forget'
+    wsconnection.clear()
+    await api.clear()
+
+
+@pytest.mark.asyncio
+async def test_extra_response():
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    error = None
+    async def get_sanity_error():
+        nonlocal error
+        error = await api.sanity_errors.pipe(op.first(),op.to_future())
+    error_task = asyncio.create_task(get_sanity_error())
+    wsconnection.data.append({"hello":"world"})
+    try:
+        await asyncio.wait_for(error_task, timeout=0.1)
+        assert str(error) == 'APIError:Extra response'
+    except asyncio.exceptions.TimeoutError:
+        assert False, "error data apppear timeout "
+    wsconnection.clear()
+    await api.clear()
+
+@pytest.mark.asyncio
+async def test_response_error():
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    r50_data = {
+        'echo_req': {'ticks': 'R_50', 'subscribe': 1},
+        'msg_type': 'tick',
+        'error': {'code': 'TestError', 'message': 'test error message'}
+    }
+    wsconnection.add_data(r50_data)
+    sub1 = await api.subscribe(r50_data['echo_req'])
+    f1 = sub1.pipe(op.first(), op.to_future())
+    with pytest.raises(ResponseError, match='ResponseError: test error message'):
+        await f1
+    r50_data = {
+        'echo_req': {'ticks': 'R_50', 'subscribe': 1},
+        'msg_type': 'tick',
+        'req_id': f1.exception().req_id,
+        'subscription': {'id': 'A111111'}
+    }
+    wsconnection.data.append(r50_data) # add back r50 again
+    #will send a `forget` if get a response again
+    await asyncio.sleep(0.1)
+    assert wsconnection.called['send'][-1] == '{"forget": "A111111", "req_id": 2}'
+    poc_data = {
+        'echo_req': {'proposal_open_contract': 1, 'subscribe': 1},
+        'msg_type': 'proposal_open_contract',
+        'error': {'code': 'TestError', 'message': 'test error message'},
+        'subscription': {'id': 'ABC11111'}
+    }
+    wsconnection.add_data(poc_data)
+    sub1 = await api.subscribe(poc_data['echo_req'])
+    response = await sub1.pipe(op.first(), op.to_future())
+    assert 'error' in response, "for the poc stream with out contract_id, the error response will not terminate the stream"
+    wsconnection.clear()
+    await api.clear()
+
+@pytest.mark.asyncio
+async def test_cache():
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    wsconnection.add_data({'ping':'pong', 'msg_type': 'ping', 'echo_req' : {'ping': 1}})
+    ping1 = await api.ping({'ping': 1})
+    assert len(wsconnection.called['send']) == 1
+    ping2 = await api.expect_response('ping')
+    assert len(wsconnection.called['send']) == 1, 'send can cache value for expect_response. get ping2 from cache, no send happen'
+    assert ping1 == ping2, "ping2 is ping1 "
+    ping3 = await api.cache.ping({'ping': 1})
+    assert len(wsconnection.called['send']) == 1, 'get ping3 from cache, no send happen'
+    assert ping1 == ping3, "ping3 is ping1 "
+    wsconnection.clear()
+    await api.clear()
+
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    wsconnection.add_data({'ping': 'pong', 'msg_type': 'ping', 'echo_req': {'ping': 1}})
+    ping1 = await api.cache.ping({'ping': 1})
+    assert len(wsconnection.called['send']) == 1
+    ping2 = await api.expect_response('ping')
+    assert len(wsconnection.called['send']) == 1, 'api.cache.ping can cache value. get ping2 from cache, no send happen'
+    assert ping1 == ping2, "ping2 is ping1 "
+    wsconnection.clear()
+    await api.clear()
+
+@pytest.mark.asyncio
+async def test_can_subscribe_one_source_many_times():
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    r50_data = {
+        'echo_req': {'ticks': 'R_50', 'subscribe': 1},
+        'msg_type': 'tick',
+        'subscription': {'id': 'A11111'}
+    }
+    wsconnection.add_data(r50_data)
+    r50_req = r50_data['echo_req']
+    r50_req.pop('subscribe');
+    sub1 = await api.subscribe(r50_req)
+    f1 = sub1.pipe(op.take(2), op.to_list(), op.to_future())
+    f2 = sub1.pipe(op.take(2), op.to_list(), op.to_future())
+    result = await asyncio.gather(f1,f2)
+    assert result == [[r50_data, r50_data],[r50_data, r50_data]]
+    await asyncio.sleep(0.01)  # wait sending 'forget' finished
+    assert wsconnection.called['send'] == [
+        '{"ticks": "R_50", "subscribe": 1, "req_id": 1}',
+        '{"forget": "A11111", "req_id": 2}']
+    wsconnection.clear()
+    await api.clear()
+
+@pytest.mark.asyncio
+async def test_reuse_poc_stream():
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    buy_data = {'echo_req': {'buy': 1, 'subscribe': 1},
+                           'subscription':  {'id': 'B111111'},
+                           'buy': {'contract_id': 1234567},
+                           'msg_type': 'proposal_open_contract'
+                           }
+    wsconnection.add_data(buy_data)
+    sub1 = await api.subscribe(buy_data['echo_req'])
+    await asyncio.sleep(0.1) # wait for setting reused stream
+    sub2 = await api.subscribe({'proposal_open_contract': 1, 'contract_id': 1234567})
+    assert id(sub1) == id(sub2)
+    assert len(api.subscription_manager.buy_key_to_contract_id) == 1
+    await api.forget('B111111')
+    assert len(api.subscription_manager.buy_key_to_contract_id) == 0
+    wsconnection.clear()
+    await api.clear()
+
+@pytest.mark.asyncio
+async def test_expect_response():
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    wsconnection.add_data({'ping':'pong', 'msg_type': 'ping', 'echo_req' : {'ping': 1}})
+    get_ping = api.expect_response('ping')
+    assert not get_ping.done(), 'get ping is a future and is pending'
+    ping_result = await api.ping({'ping': 1})
+    assert get_ping.done(), 'get ping done'
+    assert ping_result == await get_ping
+    wsconnection.clear()
+    await api.clear()
+
+@pytest.mark.asyncio
+async def test_ws_disconnect():
+    class MockedWs2(MockedWs):
+        def __init__(self):
+            self.closed = EasyFuture()
+            self.exception = ConnectionClosedOK(1000, 'test disconnect')
+            super().__init__()
+        async def close(self):
+            self.closed.resolve(self.exception)
+            pass
+        async def send(self):
+            exc = await self.closed
+            raise exc
+        async def recv(self):
+            exc = await self.closed
+            raise exc
+
+    # closed by api
+    wsconnection = MockedWs2()
+    wsconnection.exception = ConnectionClosedOK(1000, 'Closed by api')
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    await asyncio.sleep(0.1)
+    api.wsconnection_from_inside = True
+    last_error = api.sanity_errors.pipe(op.first(), op.to_future())
+    await asyncio.sleep(0.1) # waiting for init finished
+    print("here 382")
+    await api.disconnect() # it will set connected as 'Closed by disconnect', and cause MockedWs2 raising `test disconnect`
+    print("here 384")
+    assert isinstance((await last_error), ConnectionClosedOK), 'sanity error get errors'
+    print("here 386")
+    with pytest.raises(ConnectionClosedOK, match='Closed by disconnect'):
+        await api.send({'ping': 1})  # send will get same error
+    print("here 389")
+    with pytest.raises(ConnectionClosedOK, match='Closed by disconnect'):
+        await api.connected # send will get same error
+    wsconnection.clear()
+    print("here 391")
+    await api.clear()
+
+    # closed by remote
+    wsconnection = MockedWs2()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    wsconnection.exception = ConnectionClosedError(1234, 'Closed by remote')
+    last_error = api.sanity_errors.pipe(op.first(), op.to_future())
+    await asyncio.sleep(0.1) # waiting for init finished
+    await wsconnection.close() # it will set connected as 'Closed by disconnect', and cause MockedWs2 raising `test disconnect`
+    assert isinstance((await last_error), ConnectionClosedError), 'sanity error get errors'
+    with pytest.raises(ConnectionClosedError, match='Closed by remote'):
+        await api.send({'ping': 1})  # send will get same error
+    with pytest.raises(ConnectionClosedError, match='Closed by remote'):
+        await api.connected  # send will get same error
+    wsconnection.clear()
+    await api.clear()
+
+@pytest.mark.asyncio
+async def test_add_task():
+    wsconnection = MockedWs()
+    api = deriv_api.DerivAPI(connection=wsconnection)
+    exception_f = api.sanity_errors.pipe(op.first(), op.to_future())
+    async def raise_an_exception():
+        raise Exception("test add_task")
+    api.add_task(raise_an_exception(), 'raise an exception')
+    exception = await exception_f
+    assert str(exception) == 'deriv_api:raise an exception: test add_task'
+    await api.clear()
+
+def add_req_id(response, req_id):
+    response['echo_req']['req_id'] = req_id
+    response['req_id'] = req_id
+    return response
\ No newline at end of file
diff --git a/tests/test_deriv_api_calls.py b/tests/test_deriv_api_calls.py
new file mode 100644
index 0000000..55fbab1
--- /dev/null
+++ b/tests/test_deriv_api_calls.py
@@ -0,0 +1,57 @@
+import pytest
+import re
+from deriv_api.deriv_api_calls import DerivAPICalls, parse_args, validate_args
+
+class DerivedDerivAPICalls(DerivAPICalls):
+    async def send(self, args):
+        return args
+
+@pytest.mark.asyncio
+async def test_deriv_api_calls(mocker):
+    api = DerivedDerivAPICalls()
+    assert isinstance(api, DerivAPICalls)
+    assert (await api.account_closure({'account_closure': 1, 'reason': 'want'})) == {'account_closure': 1,
+                                                                                    'reason': 'want'}, 'account_closure can get right result'
+    with pytest.raises(ValueError, match='Required parameters missing: reason'):
+        await api.account_closure({})
+
+
+def test_parse_parse_args():
+    assert parse_args(
+        {'config': {'acc': {'type': 'boolean'}}, 'args': '1', 'method': 'acc', 'needs_method_arg': 1}) == {
+               'acc': 1}, "method will be a key and arg will be value if arg is not a dict and needs_method_arg is true"
+    assert parse_args(
+        {'config': {'acc': {'type': 'boolean'}}, 'args': {'acc': '0'}, 'method': 'acc', 'needs_method_arg': 1}) == {
+               'acc': 0}, "method value will from args if arg is a dict and needs_method_arg is true"
+    assert parse_args({'config': {'acc': {'type': 'string'}}, 'args': {'hello': 0}, 'method': 'acc',
+                                      'needs_method_arg': 1}) is None, "if arg is not in config, then return none"
+    # test type
+    assert parse_args(
+        {'config': {'acc': {'type': 'string'}}, 'args': {'acc': 0}, 'method': 'acc', 'needs_method_arg': 1}) == {
+               'acc': '0'}, "arg is string"
+    assert parse_args(
+        {'config': {'acc': {'type': 'numeric'}}, 'args': {'acc': '0'}, 'method': 'acc', 'needs_method_arg': 1}) == {
+               'acc': 0}, "arg is numeric"
+    assert parse_args(
+        {'config': {'acc': {'type': 'boolean'}}, 'args': {'acc': '0'}, 'method': 'acc', 'needs_method_arg': 1}) == {
+               'acc': 0}, "arg is boolean"
+
+def test_validate_args():
+    assert re.match('Requires an dict',validate_args({},""))
+    assert validate_args({'k1': {'required': 1}, 'k2': {}}, {'k1': 1, 'k2': 2, 'k3': 3}) == '', 'required keys are there'
+    error_msg = validate_args({'k1': {'required': 1}, 'k2': {'required': 1}}, {'k3': 1})
+    assert re.search('k1', error_msg) and re.search('k2', error_msg), 'missed keys will be reported'
+    config = {
+        'k1': {'type': 'dict'},
+        'k2': {'type': 'string'},
+        'k3': {'type': 'numeric'},
+        'k4': {'type': 'boolean'},
+        'k5': {}
+    }
+    error_msg = validate_args(config, {'k1': 1, 'k2': 1, 'k3': 'aString', 'k4': 'aString'})
+    assert re.search("dict value expected but found <class 'int'>: k1 ", error_msg)
+    assert re.search("string value expected but found <class 'int'>: k2", error_msg)
+    assert re.search("numeric value expected but found <class 'str'>: k3", error_msg)
+    assert re.search("boolean value expected but found <class 'str'>: k4", error_msg)
+    error_msg = validate_args(config, {'k1': {}, 'k2': "string", 'k3': 1, 'k4': True, 'k5': 1})
+    assert error_msg == ''
diff --git a/tests/test_errors.py b/tests/test_errors.py
new file mode 100644
index 0000000..6e1b020
--- /dev/null
+++ b/tests/test_errors.py
@@ -0,0 +1,11 @@
+from deriv_api.errors import APIError, ConstructionError
+
+def test_app_error_class():
+    error = APIError("A error")
+    assert isinstance(error, Exception)
+    assert f'{error}' == 'APIError:A error'
+
+def test_construction_error_class():
+    error = ConstructionError("A error")
+    assert isinstance(error, Exception)
+    assert f'{error}' == 'ConstructionError:A error'
diff --git a/tests/test_in_memory.py b/tests/test_in_memory.py
new file mode 100644
index 0000000..7277172
--- /dev/null
+++ b/tests/test_in_memory.py
@@ -0,0 +1,11 @@
+from deriv_api.in_memory import InMemory
+
+
+def test_in_memory():
+    obj = InMemory()
+    assert isinstance(obj, InMemory)
+    obj.set('hello', {'msg_type': 'test_type', 'val': 123})
+    assert obj.has('hello')
+    assert obj.get('hello')['val'] == 123
+    assert obj.get_by_msg_type('test_type') == obj.get('hello')
+    assert obj.has('no such key') is False
diff --git a/tests/test_subscription_manager.py b/tests/test_subscription_manager.py
new file mode 100644
index 0000000..bdbfa14
--- /dev/null
+++ b/tests/test_subscription_manager.py
@@ -0,0 +1,114 @@
+import pytest
+
+from deriv_api.subscription_manager import SubscriptionManager, get_msg_type
+from rx.subject import Subject
+from rx import Observable
+import asyncio
+from deriv_api.errors import APIError
+
+mocked_response = {}
+
+
+class API:
+    def __init__(self):
+        self.mocked_response = {}
+        self.send_and_get_source_request = {}
+        self.send_request = {}
+        self.send_and_get_source_called = 0
+        self.send_called = 0
+
+    def send_and_get_source(self, request: dict) -> Subject:
+        self.subject = Subject()
+        self.send_and_get_source_called = self.send_and_get_source_called + 1
+        self.send_and_get_source_request[self.send_and_get_source_called] = request
+        return self.subject
+
+    async def send(self, request: dict):
+        self.send_called = self.send_called + 1
+        self.send_request[self.send_called] = request
+        return request
+
+    async def emit(self):
+        await asyncio.sleep(0.1)
+        self.subject.on_next(self.mocked_response)
+
+    def sanity_errors(self):
+        class SanityErrors:
+            def next(self, error):
+                return error
+        return SanityErrors()
+    def add_task(self, task, name):
+        asyncio.create_task(task, name=f"mocked_api:{name}")
+
+def test_get_msg_type():
+    assert get_msg_type({'hello': 1}) is None
+    assert get_msg_type({'proposal': 1}) == 'proposal'
+
+
+@pytest.mark.asyncio
+async def test_subscribe():
+    api: API = API()
+    subscription_manager = SubscriptionManager(api)
+    subs_id = 'ID11111'
+    api.mocked_response = {"msg_type": "proposal", 'subscription': {'id': subs_id}}
+    assert not subscription_manager.source_exists({'proposal': 1}), "at start there is no such source"
+    with pytest.raises(APIError, match='Subscription type is not found in deriv-api'):
+        await subscription_manager.subscribe({"no such type"})
+    # get source first time
+    source, emit = await asyncio.gather(subscription_manager.subscribe({'proposal': 1}), api.emit())
+    assert isinstance(source, Observable)
+    assert api.send_and_get_source_called == 1
+    assert api.send_and_get_source_request[1] == {'proposal': 1, 'subscribe': 1}
+    assert api.send_request == {}
+    # get source second time
+    api.__init__()
+    source2, emit = await asyncio.gather(subscription_manager.subscribe({'proposal': 1}), api.emit())
+    assert api.send_and_get_source_called == 0
+    assert (source is source2), "same result"
+    assert (source is subscription_manager.get_source({'proposal': 1})), 'source is in the cache'
+    assert subscription_manager.source_exists({'proposal': 1}), "source in the cache"
+    forget_result = await subscription_manager.forget(subs_id)
+    assert api.send_called == 1
+    assert forget_result == {'forget': subs_id}
+    assert api.subject.is_disposed, "source is disposed"
+
+    # test buy subscription
+    api.__init__()
+    subs_id = 'ID22222'
+    api.mocked_response = {
+        "buy": {
+            "contract_id": 12345
+        },
+        "msg_type": "buy",
+        "subscription": {
+            "id": subs_id
+        }
+    }
+
+    request = {
+        "buy": 1,
+        "price": 100,
+    }
+    source, emit = await asyncio.gather(subscription_manager.subscribe(request), api.emit())
+    assert api.send_and_get_source_called == 1 , "send_and_get_source called once"
+    assert isinstance(source, Observable)
+    request = {
+        'proposal_open_contract': 1,
+        'contract_id': 12345
+    }
+    api.__init__()
+    source2, emit = await asyncio.gather(subscription_manager.subscribe(request), api.emit())
+    assert api.send_and_get_source_called == 0 , "send_and_get_source not called"
+    assert source is source2, '"buy" source and "proposal_open_contract" source are same one and cached'
+    await subscription_manager.forget(subs_id)
+    api.__init__()
+    source2, emit = await asyncio.gather(subscription_manager.subscribe(request), api.emit())
+    assert api.send_and_get_source_called == 1 , "cache is cleared so the new call will get"
+    assert source2 is not source, "new source is not the old one"
+
+@pytest.mark.asyncio
+async def test_forget_all():
+    api = API()
+    subscription_manager = SubscriptionManager(api)
+    result = await subscription_manager.forget_all("hello")
+    assert result == {'forget_all': ["hello"]}
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..5ac5ae9
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,6 @@
+from deriv_api.utils import dict_to_cache_key
+import pickle
+
+
+def test_dict_to_cache_key():
+    assert(pickle.loads(dict_to_cache_key({"hello": "world", "subscribe": 1, "passthrough": 1, "req_id": 1})) == {"hello": "world"})