├── .travis.yml ├── tests ├── __init__.py └── test_wswrapper.py ├── .python-version ├── docs ├── .python-version ├── source │ ├── authors.rst │ ├── readme.rst │ ├── changelog.rst │ ├── contributing.rst │ ├── modules.rst │ ├── usage.rst │ ├── installation.rst │ ├── wealthsimple.rst │ ├── index.rst │ └── conf.py ├── Makefile └── make.bat ├── requirements.txt ├── src └── wealthsimple │ ├── __init__.py │ ├── requestor.py │ └── wealthsimple.py ├── CHANGELOG.rst ├── AUTHORS.rst ├── .gitignore ├── LICENSE ├── setup.py ├── CONTRIBUTING.rst └── README.rst /.travis.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | AI 2 | -------------------------------------------------------------------------------- /docs/.python-version: -------------------------------------------------------------------------------- 1 | system 2 | -------------------------------------------------------------------------------- /docs/source/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/source/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../README.rst 2 | -------------------------------------------------------------------------------- /docs/source/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../CHANGELOG.rst 2 | -------------------------------------------------------------------------------- /docs/source/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /tests/test_wswrapper.py: -------------------------------------------------------------------------------- 1 | from ..wswrapper.wealthsimple import WSTrade 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cloudscraper==1.2.56 2 | vcrpy 3 | pytest 4 | flake8 5 | black 6 | -------------------------------------------------------------------------------- /src/wealthsimple/__init__.py: -------------------------------------------------------------------------------- 1 | from .wealthsimple import WSTrade 2 | 3 | from .requestor import APIRequestor 4 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | wealthsimple 2 | ============ 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | wealthsimple 8 | -------------------------------------------------------------------------------- /docs/source/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use Wealthsimple-Trade-Python in a project:: 6 | 7 | import wealthsimple_trade_python 8 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | :: 6 | 7 | pip install wealthsimple-trade-python 8 | 9 | You can also install the in-development version with:: 10 | 11 | pip install https://github.com/seansullivan44/Wealthsimple-Trade-Python/archive/master.zip -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | 2 | Changelog 3 | ========= 4 | 5 | 1.1.0 (2021-03-14) 6 | ------------------ 7 | * Swap out `requests.Session` with `cloudscraper.CloudScraper` instance to overcome recent security changes related to Cloudflare 8 | 9 | 1.0.0 (2020-06-24) 10 | ------------------ 11 | * First release on PyPI. 12 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | 2 | Authors 3 | ======= 4 | 5 | Sean Sullivan (Project Creator) 6 | ******************************* 7 | * Carleton University - Electrical Engineering 8 | * Email: seansullivan3@cmail.carleton.ca 9 | * Website: seanmcsullivan.ca 10 | 11 | NicBT 12 | ******************************* 13 | * GitHub: https://github.com/NicBT 14 | 15 | Joey Orlando 16 | ******************************* 17 | * GitHub: https://github.com/joeyorlando 18 | -------------------------------------------------------------------------------- /docs/source/wealthsimple.rst: -------------------------------------------------------------------------------- 1 | wealthsimple package 2 | ==================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | wealthsimple.requestor module 8 | ----------------------------- 9 | 10 | .. automodule:: wealthsimple.requestor 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | wealthsimple.wealthsimple module 16 | -------------------------------- 17 | 18 | .. automodule:: wealthsimple.wealthsimple 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | 24 | Module contents 25 | --------------- 26 | 27 | .. automodule:: wealthsimple 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Wealthsimple-Trade-Python documentation master file, created by 2 | sphinx-quickstart on Fri Jun 19 17:48:20 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Wealthsimple-Trade-Python 7 | ===================================================== 8 | A convenient Python wrapper for the Wealthsimple Trade API. Note that this wrapper is Unofficial and is not in any way affiliated with Wealthsimple. Please use at your own risk. 9 | 10 | The Wealthsimple-Trade-Python Project: 11 | -------------------------------------- 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | readme 17 | contributing 18 | authors 19 | changelog 20 | 21 | Code Documentation 22 | ------------------ 23 | .. toctree:: 24 | :maxdepth: 1 25 | 26 | wealthsimple 27 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | __pycache__ 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | .eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | wheelhouse 19 | develop-eggs 20 | .installed.cfg 21 | lib 22 | lib64 23 | venv*/ 24 | pyvenv*/ 25 | pip-wheel-metadata/ 26 | 27 | # Installer logs 28 | pip-log.txt 29 | 30 | # Unit test / coverage reports 31 | .coverage 32 | .tox 33 | .coverage.* 34 | .pytest_cache/ 35 | nosetests.xml 36 | coverage.xml 37 | htmlcov 38 | 39 | # Translations 40 | *.mo 41 | 42 | # Mr Developer 43 | .mr.developer.cfg 44 | .project 45 | .pydevproject 46 | .idea 47 | *.iml 48 | *.komodoproject 49 | 50 | # Complexity 51 | output/*.html 52 | output/*/index.html 53 | 54 | # Sphinx 55 | docs/_build 56 | 57 | .DS_Store 58 | *~ 59 | .*.sw[po] 60 | .build 61 | .ve 62 | .env 63 | .cache 64 | .pytest 65 | .benchmarks 66 | .bootstrap 67 | .appveyor.token 68 | *.bak 69 | 70 | # Mypy Cache 71 | .mypy_cache/ 72 | 73 | # IDEs 74 | .vscode 75 | 76 | # Mac OS 77 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, Sean Sullivan 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | master_doc = 'index' 14 | 15 | import os 16 | import sys 17 | 18 | sys.path.insert(0, os.path.abspath("../../src/")) 19 | 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = "Wealthsimple-Trade-Python" 24 | copyright = "2020, Sean Sullivan" 25 | author = "Sean Sullivan" 26 | 27 | # The full version, including alpha/beta/rc tags 28 | release = "1.0.0" 29 | 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = ["sphinx.ext.autodoc"] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ["_templates"] 40 | 41 | # List of patterns, relative to source directory, that match files and 42 | # directories to ignore when looking for source files. 43 | # This pattern also affects html_static_path and html_extra_path. 44 | exclude_patterns = [] 45 | 46 | 47 | # -- Options for HTML output ------------------------------------------------- 48 | 49 | # The theme to use for HTML and HTML Help pages. See the documentation for 50 | # a list of builtin themes. 51 | # 52 | html_theme = "sphinx_rtd_theme" 53 | 54 | # Add any paths that contain custom static files (such as style sheets) here, 55 | # relative to this directory. They are copied after the builtin static files, 56 | # so a file named "default.css" will overwrite the builtin "default.css". 57 | html_static_path = ["_static"] 58 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | from __future__ import absolute_import 4 | from __future__ import print_function 5 | 6 | import io 7 | import re 8 | from glob import glob 9 | from os.path import basename 10 | from os.path import dirname 11 | from os.path import join 12 | from os.path import splitext 13 | 14 | from setuptools import find_packages 15 | from setuptools import setup 16 | 17 | """ 18 | def read(*names, **kwargs): 19 | with io.open( 20 | join(dirname(__file__), *names), encoding=kwargs.get("encoding", "utf8") 21 | ) as fh: 22 | return fh.read() 23 | """ 24 | 25 | with open("README.rst", "r") as fh: 26 | long_description = fh.read() 27 | 28 | setup( 29 | name="wealthsimple-trade-python", 30 | version="1.1.0", 31 | license="MIT", 32 | description="Python wrapper for the Wealthsimple Trade API", 33 | long_description=long_description, 34 | long_description_content_type="text/x-rst", 35 | author="Sean Sullivan", 36 | author_email="sean.mc.sullivan@gmail.com", 37 | url="https://github.com/seansullivan44/Wealthsimple-Trade-Python", 38 | packages=find_packages("src"), 39 | package_dir={"": "src"}, 40 | py_modules=[splitext(basename(path))[0] for path in glob("src/*.py")], 41 | include_package_data=True, 42 | zip_safe=False, 43 | classifiers=[ 44 | "Development Status :: 5 - Production/Stable", 45 | "Intended Audience :: Developers", 46 | "License :: OSI Approved :: MIT License", 47 | "Operating System :: Unix", 48 | "Operating System :: POSIX", 49 | "Operating System :: Microsoft :: Windows", 50 | "Programming Language :: Python", 51 | "Programming Language :: Python :: 3", 52 | "Programming Language :: Python :: 3.5", 53 | "Programming Language :: Python :: 3.6", 54 | "Programming Language :: Python :: 3.7", 55 | "Programming Language :: Python :: 3.8", 56 | "Programming Language :: Python :: Implementation :: CPython", 57 | "Programming Language :: Python :: Implementation :: PyPy", 58 | "Topic :: Utilities", 59 | ], 60 | project_urls={ 61 | "Documentation": "https://Wealthsimple-Trade-Python.readthedocs.io/", 62 | "Changelog": "https://Wealthsimple-Trade-Python.readthedocs.io/en/latest/changelog.html", 63 | "Issue Tracker": "https://github.com/seansullivan44/Wealthsimple-Trade-Python/issues", 64 | }, 65 | keywords=["wealthsimple", "trade", "finance", 66 | "stocks", "market", "api", "wrapper"], 67 | python_requires="!=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", 68 | install_requires=["cloudscraper", ], 69 | ) 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | Bug reports 9 | =========== 10 | 11 | When `reporting a bug `_ please include: 12 | 13 | * Your operating system name and version. 14 | * Any details about your local setup that might be helpful in troubleshooting. 15 | * Detailed steps to reproduce the bug. 16 | 17 | Documentation improvements 18 | ========================== 19 | 20 | Wealthsimple-Trade-Python could always use more documentation, whether as part of the 21 | official Wealthsimple-Trade-Python docs, in docstrings, or even on the web in blog posts, 22 | articles, and such. 23 | 24 | Feature requests and feedback 25 | ============================= 26 | 27 | The best way to send feedback is to file an issue at https://github.com/seansullivan44/Wealthsimple-Trade-Python/issues. 28 | 29 | If you are proposing a feature: 30 | 31 | * Explain in detail how it would work. 32 | * Keep the scope as narrow as possible, to make it easier to implement. 33 | * Remember that this is a volunteer-driven project, and that code contributions are welcome :) 34 | 35 | Development 36 | =========== 37 | 38 | To set up `Wealthsimple-Trade-Python` for local development: 39 | 40 | 1. Fork `Wealthsimple-Trade-Python `_ 41 | (look for the "Fork" button). 42 | 2. Clone your fork locally:: 43 | 44 | git clone git@github.com:seansullivan44/Wealthsimple-Trade-Python.git 45 | 46 | 3. Create a branch for local development:: 47 | 48 | git checkout -b name-of-your-bugfix-or-feature 49 | 50 | Now you can make your changes locally. 51 | 52 | 4. Commit your changes and push your branch to GitHub:: 53 | 54 | git add . 55 | git commit -m "Your detailed description of your changes." 56 | git push origin name-of-your-bugfix-or-feature 57 | 58 | 5. Submit a pull request through the GitHub website. 59 | 60 | Pull Request Guidelines 61 | ----------------------- 62 | 63 | If you need some code review or feedback while you're developing the code just make the pull request. 64 | 65 | For merging, you should: 66 | 67 | 1. Update documentation when there's new API, functionality etc. 68 | 2. Add a note to ``CHANGELOG.rst`` about the changes. 69 | 3. Add yourself to ``AUTHORS.rst``. 70 | 71 | .. [1] If you don't have all the necessary python versions available locally you can rely on Travis - it will 72 | `run the tests `_ for each change you add in the pull request. 73 | 74 | It will be slower though ... 75 | -------------------------------------------------------------------------------- /src/wealthsimple/requestor.py: -------------------------------------------------------------------------------- 1 | class APIRequestor: 2 | """ 3 | A class to simplify request calls to REST API 4 | 5 | Attributes 6 | ---------- 7 | session : session 8 | A requests Session object to be associated with the class 9 | APIMainURL : str 10 | Main URL endpoint for API 11 | 12 | Methods 13 | ------- 14 | makeRequest(method, endpoint, params=None, returnValue=None) 15 | Make a request to a given API endpoint 16 | post(URL, params=None) 17 | Make a POST request to a given API endpoint 18 | get(URL, params=None) 19 | Make a GET request to a given API endpoint 20 | """ 21 | 22 | def __init__(self, session, APIMainURL): 23 | """ 24 | Parameters 25 | ---------- 26 | session : Session 27 | A requests Session object to be associated with the class 28 | APIMainURL : str 29 | Main URL endpoint for API 30 | """ 31 | self.session = session 32 | self.APIMainURL = APIMainURL 33 | 34 | def makeRequest(self, method, endpoint, params=None, returnValue=None): 35 | """Make a request to a given API endpoint 36 | 37 | Parameters 38 | ---------- 39 | method : str 40 | Specify POST or GET request 41 | endpoint : str 42 | URL endpoint oof API (Does not include base URL) 43 | params : dict 44 | Dictionary of parameters to be passed with request 45 | 46 | Returns 47 | ------- 48 | Response : Response 49 | A requests response object 50 | """ 51 | URL = self.APIMainURL + endpoint 52 | 53 | if method == "POST": 54 | return self.post(URL, params) 55 | elif method == "GET": 56 | return self.get(URL, params) 57 | else: 58 | raise Exception(f"Invalid request method: {method}") 59 | 60 | def post(self, URL, params=None): 61 | """Make a POST request to a given API endpoint 62 | 63 | Parameters 64 | ---------- 65 | URL : str 66 | Full URL endpoint of API 67 | params : dict 68 | Dictionary of parameters to be passed with request 69 | 70 | Returns 71 | ------- 72 | Response : Response 73 | A requests response object 74 | """ 75 | 76 | try: 77 | return self.session.post(URL, params) 78 | except Exception as err: 79 | print(err) 80 | 81 | def get(self, URL, params=None): 82 | """Make a GET request to a given API endpoint 83 | 84 | Parameters 85 | ---------- 86 | URL : str 87 | Full URL endpoint of API 88 | params : dict 89 | Dictionary of parameters to be passed with request 90 | 91 | Returns 92 | ------- 93 | Response : Response 94 | A requests response object 95 | """ 96 | auth = self.session.headers["Authorization"] 97 | response = self.session.get(URL, headers={"Authorization": auth}) 98 | return response 99 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Wealthsimple Trade Python 3 | ========================= 4 | A convenient Python wrapper for the Wealthsimple Trade API. Note that this wrapper is Unofficial and is not in any way affiliated with Wealthsimple. Please use at your own risk. 5 | 6 | .. raw:: html 7 | 8 | 9 |
10 | 11 | 12 |
13 | 14 | | 15 | .. start-badges 16 | 17 | .. list-table:: 18 | :stub-columns: 1 19 | 20 | * - docs 21 | - |docs| 22 | * - tests 23 | - | |travis| |requires| 24 | * - package 25 | - | |commits-since| 26 | .. |docs| image:: https://readthedocs.org/projects/wealthsimple-trade-python/badge/?version=latest 27 | :target: https://wealthsimple-trade-python.readthedocs.io/en/latest/?badge=latest 28 | :alt: Documentation Status 29 | 30 | .. |travis| image:: https://api.travis-ci.org/seansullivan44/Wealthsimple-Trade-Python.svg?branch=master 31 | :alt: Travis-CI Build Status 32 | :target: https://travis-ci.org/seansullivan44/Wealthsimple-Trade-Python 33 | 34 | .. |requires| image:: https://requires.io/github/seansullivan44/Wealthsimple-Trade-Python/requirements.svg?branch=master 35 | :alt: Requirements Status 36 | :target: https://requires.io/github/seansullivan44/Wealthsimple-Trade-Python/requirements/?branch=master 37 | 38 | .. |commits-since| image:: https://img.shields.io/pypi/v/wealthsimple-trade-python 39 | :alt: Commits since latest release 40 | :target: https://pypi.org/project/wealthsimple-trade-python/ 41 | 42 | .. end-badges 43 | 44 | 45 | Installation 46 | ============ 47 | 48 | :: 49 | 50 | pip install wealthsimple-trade-python 51 | 52 | You can also install the in-development version with:: 53 | 54 | pip install https://github.com/seansullivan44/Wealthsimple-Trade-Python/archive/master.zip 55 | 56 | *Note*: `node` is a dependency of this project. See [here](https://github.com/VeNoMouS/cloudscraper#dependencies) for more information. 57 | 58 | Getting Started 59 | =============== 60 | Download the Wealthsimple Trade app for iOS or Android and create an account. This API wrapper will use your Wealthsimple Trade login credentials to make successful API calls. After creating an account, use your login credentials to create a WSTrade object: 61 | :: 62 | 63 | import wealthsimple 64 | WS = wealthsimple.WSTrade('email', 'password') 65 | 66 | If your Wealthsimple Trade account uses two-factor authentication then you must provide the `WSTrade` object with a callback function as shown in the following example: 67 | :: 68 | 69 | import wealthsimple 70 | 71 | def my_two_factor_function(): 72 | MFACode = "" 73 | while not MFACode: 74 | # Obtain user input and ensure it is not empty 75 | MFACode = input("Enter 2FA code: ") 76 | return MFACode 77 | 78 | ws = wealthsimple.WSTrade( 79 | "email", 80 | "password", 81 | two_factor_callback=my_two_factor_function, 82 | ) 83 | 84 | Documentation 85 | ============= 86 | 87 | 88 | https://Wealthsimple-Trade-Python.readthedocs.io/ 89 | 90 | -------------------------------------------------------------------------------- /src/wealthsimple/wealthsimple.py: -------------------------------------------------------------------------------- 1 | from .requestor import APIRequestor 2 | import cloudscraper 3 | import os 4 | import json 5 | 6 | 7 | class WSTrade: 8 | """ 9 | Wealthsimple Trade API wrapper 10 | 11 | Attributes 12 | ---------- 13 | session : session 14 | A requests Session object to be associated with the class 15 | APIMainURL : str 16 | Main URL endpoint for API 17 | TradeAPI : APIRequester 18 | APIRequester object to handle API calls 19 | 20 | Methods 21 | ------- 22 | login(email=None, password=None, two_factor_callback=None) 23 | Login to Wealthsimple Trade account 24 | get_accounts() 25 | Get Wealthsimple Trade accounts 26 | get_account_ids() 27 | Get Wealthsimple Trade account ids 28 | get_account(id) 29 | Get a Wealthsimple Trade account given an id 30 | get_account_history(id, time="all") 31 | Get Wealthsimple Trade account history 32 | get_activities() 33 | Get Wealthsimple Trade activities 34 | get_orders(symbol=None) 35 | Get Wealthsimple Trade orders 36 | get_security(symbol) 37 | Get information about a security 38 | get_positions(id) 39 | Get positions 40 | get_person() 41 | Get Wealthsimple Trade person object 42 | get_me() 43 | Get Wealthsimple Trade user object 44 | get_bank_accounts(): 45 | Get list of bank accounts tied to Wealthsimple Trade account 46 | get_deposits() 47 | Get list of deposits 48 | get_forex() 49 | Get foreign exchange rate 50 | """ 51 | 52 | def __init__(self, email: str, password: str, two_factor_callback: callable = None): 53 | """ 54 | Parameters 55 | ---------- 56 | email : str 57 | Wealthsimple Trade login email 58 | password : str 59 | Wealthsimple Trade login password 60 | two_factor_callback: function 61 | Callback function that returns user input for 2FA code 62 | """ 63 | self.session = cloudscraper.create_scraper() 64 | self.APIMAIN = "https://trade-service.wealthsimple.com/" 65 | self.TradeAPI = APIRequestor(self.session, self.APIMAIN) 66 | self.login(email, password, two_factor_callback=two_factor_callback) 67 | 68 | def login( 69 | self, 70 | email: str = None, 71 | password: str = None, 72 | two_factor_callback: callable = None, 73 | ) -> None: 74 | """Login to Wealthsimple Trade account 75 | 76 | Parameters 77 | ---------- 78 | email : str 79 | Wealthsimple Trade account email 80 | password : str 81 | Wealthsimple Trade account password 82 | two_factor_callback: function 83 | Callback function that returns user input for 2FA code 84 | 85 | Returns 86 | ------- 87 | None 88 | """ 89 | if email and password: 90 | 91 | # Login credentials to pass in request 92 | data = [ 93 | ("email", email), 94 | ("password", password), 95 | ] 96 | 97 | response = self.TradeAPI.makeRequest("POST", "auth/login", data) 98 | 99 | # Check if account requires 2FA 100 | if "x-wealthsimple-otp" in response.headers: 101 | if two_factor_callback == None: 102 | raise Exception( 103 | "This account requires 2FA. A 2FA callback function must be provided" 104 | ) 105 | else: 106 | # Obtain 2FA code using callback function 107 | MFACode = two_factor_callback() 108 | # Add the 2FA code to the body of the login request 109 | data.append(("otp", MFACode)) 110 | # Make a second login request using the 2FA code 111 | response = self.TradeAPI.makeRequest( 112 | "POST", "auth/login", data) 113 | 114 | if response.status_code == 401: 115 | raise Exception("Invalid Login") 116 | 117 | # Update session headers with the API access token 118 | self.session.headers.update( 119 | {"Authorization": response.headers["X-Access-Token"]} 120 | ) 121 | else: 122 | raise Exception("Missing login credentials") 123 | 124 | def get_accounts(self) -> list: 125 | """Get Wealthsimple Trade accounts 126 | 127 | Returns 128 | ------- 129 | list 130 | A list of Wealthsimple Trade account dictionary objects 131 | """ 132 | response = self.TradeAPI.makeRequest("GET", "account/list") 133 | response = response.json() 134 | response = response["results"] 135 | return response 136 | 137 | def get_account_ids(self) -> list: 138 | """Get Wealthsimple Trade account ids 139 | 140 | Returns 141 | ------- 142 | list 143 | A list of Wealthsimple Trade account ids 144 | """ 145 | userAccounts = self.get_accounts() 146 | accountIDList = [] 147 | for account in userAccounts: 148 | accountIDList.append(account["id"]) 149 | return accountIDList 150 | 151 | def get_account(self, id: str) -> dict: 152 | """Get a Wealthsimple Trade account given an id 153 | 154 | Parameters 155 | ---------- 156 | id : str 157 | Wealthsimple Trade account id 158 | 159 | Returns 160 | ------- 161 | dict 162 | A dictionary containing the Wealthsimple Trade account 163 | """ 164 | userAccounts = self.get_accounts() 165 | for account in userAccounts: 166 | if account["id"] == id: 167 | return account 168 | raise NameError(f"{id} does not correspond to any account") 169 | 170 | def get_account_history(self, id: str, time: str = "all") -> dict: 171 | """Get Wealthsimple Trade account history 172 | 173 | Parameters 174 | ---------- 175 | id : str 176 | Wealthsimple Trade account id 177 | time : str 178 | String containing time interval for history 179 | 180 | Returns 181 | ------- 182 | dict 183 | A dictionary containing the historical Trade account data 184 | """ 185 | response = self.TradeAPI.makeRequest( 186 | "GET", f"account/history/{time}?account_id={id}" 187 | ) 188 | response = response.json() 189 | if "error" in response: 190 | if response["error"] == "Record not found": 191 | raise NameError(f"{id} does not correspond to any account") 192 | 193 | return response 194 | 195 | def get_activities(self) -> list: 196 | """Get Wealthsimple Trade activities 197 | 198 | Returns 199 | ------- 200 | list 201 | A list of dictionaries containing Wealthsimple Trade activities 202 | """ 203 | response = self.TradeAPI.makeRequest("GET", "account/activities") 204 | response = response.json() 205 | return response["results"] 206 | 207 | def get_orders(self, symbol: str = None) -> list: 208 | """Get Wealthsimple Trade orders 209 | 210 | Parameters 211 | ---------- 212 | symbol : str 213 | Symbol for security to filter orders on 214 | 215 | Returns 216 | ------- 217 | list 218 | A list containing Wealthsimple Trade order dictionaries 219 | """ 220 | response = self.TradeAPI.makeRequest("GET", "orders") 221 | response = response.json() 222 | # Check if order must be filtered: 223 | if symbol: 224 | filteredOrders = [] 225 | for order in response["results"]: 226 | if order["symbol"] == symbol: 227 | filteredOrders.append(order) 228 | return filteredOrders 229 | else: 230 | return response 231 | 232 | def get_security(self, id: str) -> dict: 233 | """Get information about a security 234 | 235 | Parameters 236 | ---------- 237 | id : str 238 | Wealthsimple Security ID to search on 239 | 240 | Returns 241 | ------- 242 | dict 243 | Dictionary containing information for security 244 | """ 245 | 246 | response = self.TradeAPI.makeRequest("GET", f"securities/{id}") 247 | response = response.json() 248 | return response 249 | 250 | def get_securities_from_ticker(self, symbol: str) -> list: 251 | """Get information about a securitys with matching ticker symbols 252 | 253 | Parameters 254 | ---------- 255 | symbol : str 256 | Symbol for security to search on 257 | 258 | Returns 259 | ------- 260 | list 261 | A list containing matching securities 262 | """ 263 | response = self.TradeAPI.makeRequest( 264 | "GET", f"securities?query={symbol}") 265 | response = response.json() 266 | return response["results"] 267 | 268 | def get_positions(self, id: str) -> list: 269 | """Get positions 270 | 271 | Parameters 272 | ---------- 273 | id : str 274 | Wealthsimple Trade account id 275 | 276 | Returns 277 | ------- 278 | list 279 | A list containing positions 280 | """ 281 | response = self.TradeAPI.makeRequest( 282 | "GET", f"account/positions?account_id={id}" 283 | ) 284 | response = response.json() 285 | return response["results"] 286 | 287 | def get_person(self) -> dict: 288 | """Get Wealthsimple Trade person object 289 | 290 | Returns 291 | ------- 292 | dict 293 | A dictionary containing a person object 294 | """ 295 | response = self.TradeAPI.makeRequest("GET", "person") 296 | return response.json() 297 | 298 | def get_me(self) -> dict: 299 | """Get Wealthsimple Trade user object 300 | 301 | Returns 302 | ------- 303 | dict 304 | A dictionary containing a user object 305 | """ 306 | response = self.TradeAPI.makeRequest("GET", "me") 307 | return response.json() 308 | 309 | def get_bank_accounts(self) -> list: 310 | """Get list of bank accounts tied to Wealthsimple Trade account 311 | 312 | Returns 313 | ------- 314 | list 315 | A list of dictionaries containing bank account objects 316 | """ 317 | response = self.TradeAPI.makeRequest("GET", "bank-accounts") 318 | response = response.json() 319 | return response["results"] 320 | 321 | def get_deposits(self) -> list: 322 | """Get list of deposits 323 | 324 | Returns 325 | ------- 326 | list 327 | A list of dictionaries containing deposit objects 328 | """ 329 | response = self.TradeAPI.makeRequest("GET", "deposits") 330 | response = response.json() 331 | return response["results"] 332 | 333 | def get_forex(self) -> dict: 334 | """Get foreign exchange rate 335 | 336 | Returns 337 | ------- 338 | dict 339 | A dictionary containing foreign exchange rates 340 | """ 341 | response = self.TradeAPI.makeRequest("GET", "forex") 342 | return response.json() 343 | --------------------------------------------------------------------------------