Access your Wealthsimple account using their (GraphQL) API.
Project description
Unofficial Wealthsimple API Library for Python
This library allows you to access your own account using the Wealthsimple (GraphQL) API using Python.
Installation
uv add ws-api
# or
pip install ws-api
Usage
Note: You'll need the keyring package to run the code below. Install with: uv add keyring (or pip install keyring)
from datetime import datetime
import json
import keyring
import os
import tempfile
from ws_api import WealthsimpleAPI, OTPRequiredException, LoginFailedException, WSAPISession
class WSApiTest:
def main(self):
# 1. Define a function that will be called when the session is created or updated. Persist the session to a safe place, like in the keyring
keyring_service_name = "foo.bar"
persist_session_fct = lambda sess, uname: keyring.set_password(f"{keyring_service_name}.{uname}", "session", sess)
# The session contains tokens that can be used to empty your Wealthsimple account, so treat it with respect!
# i.e. don't store it in a Git repository, or anywhere it can be accessed by others!
# If you want, you can set a custom User-Agent for the requests to the WealthSimple API:
WealthsimpleAPI.set_user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36")
# 2. If it's the first time you run this, create a new session using the username & password (and TOTP answer, if needed). Do NOT save those infos in your code!
username = input("Wealthsimple username (email): ")
session = keyring.get_password(f"{keyring_service_name}.{username}", "session")
if session:
session = WSAPISession.from_json(session)
if not session:
username = None
password = None
otp_answer = None
while True:
try:
if not username:
username = input("Wealthsimple username (email): ")
session = keyring.get_password(f"{keyring_service_name}.{username}", "session")
if session:
session = WSAPISession.from_json(session)
break
if not password:
password = input("Password: ")
WealthsimpleAPI.login(username, password, otp_answer, persist_session_fct=persist_session_fct)
# The above will throw exceptions if login failed
# So we break (out of the login "while True" loop) on success:
session = WSAPISession.from_json(keyring.get_password(keyring_service_name, "session"))
break
except OTPRequiredException:
otp_answer = input("TOTP code: ")
except LoginFailedException:
print("Login failed. Try again.")
username = None
password = None
# 3. Use the session object to instantiate the API object
ws = WealthsimpleAPI.from_token(session, persist_session_fct, username)
# persist_session_fct is needed here too, because the session may be updated if the access token expired, and thus this function will be called to save the new session
# Optionally define functions to cache market data, if you want transactions' descriptions and accounts balances to show the security's symbol instead of its ID
# eg. sec-s-e7947deb977341ff9f0ddcf13703e9a6 => TSX:XEQT
def sec_info_getter_fn(ws_security_id: str):
cache_file_path = os.path.join(tempfile.gettempdir(), f"ws-api-{ws_security_id}.json")
if os.path.exists(cache_file_path):
return json.load(open(cache_file_path, 'r'))
return None
def sec_info_setter_fn(ws_security_id: str, market_data: object):
cache_file_path = os.path.join(tempfile.gettempdir(), f"ws-api-{ws_security_id}.json")
# noinspection PyTypeChecker
json.dump(market_data, open(cache_file_path, 'w'))
return market_data
ws.set_security_market_data_cache(sec_info_getter_fn, sec_info_setter_fn)
# 4. Use the API object to access your WS accounts
accounts = ws.get_accounts()
print("All Accounts Historical Value & Gains:")
historical_fins = ws.get_identity_historical_financials([a['id'] for a in accounts])
for hf in historical_fins:
value = float(hf['netLiquidationValueV2']['amount'])
deposits = float(hf['netDepositsV2']['amount'])
gains = value - deposits
print(f" - {hf['date']} = ${value:,.0f} - {deposits:,.0f} (deposits) = {gains:,.0f} (gains)")
for account in accounts:
print(f"Account: {account['description']} ({account['number']})")
if account['description'] == account['unifiedAccountType']:
# This is an "unknown" account, for which description is generic; please open an issue on https://github.com/gboudreau/ws-api-python/issues and include the following:
print(f" Unknown account: {account}")
if account['currency'] == 'CAD':
value = account['financials']['currentCombined']['netLiquidationValue']['amount']
print(f" Net worth: {value} {account['currency']}")
# Note: For USD accounts, value is the CAD value converted to USD
# For USD accounts, only the balance & positions are relevant
# Cash and positions balances
balances = ws.get_account_balances(account['id'])
cash_balance_key = 'sec-c-usd' if account['currency'] == 'USD' else 'sec-c-cad'
cash_balance = float(balances.get(cash_balance_key, 0))
print(f" Available (cash) balance: {cash_balance} {account['currency']}")
if len(balances) > 1:
print(" Assets:")
for security, bal in balances.items():
if security in ['sec-c-cad', 'sec-c-usd']:
continue
print(f" - {security} x {bal}")
print(" Historical Value & Gains:")
historical_fins = ws.get_account_historical_financials(account['id'], account['currency'])
for hf in historical_fins:
value = hf['netLiquidationValueV2']['cents'] / 100
deposits = hf['netDepositsV2']['cents'] / 100
gains = value - deposits
print(f" - {hf['date']} = ${value:,.0f} - {deposits:,.0f} (deposits) = {gains:,.0f} (gains)")
# Fetch activities (transactions)
acts = ws.get_activities(account['id'])
if acts:
print(" Transactions:")
acts.reverse() # Activities are sorted by OCCURRED_AT_DESC by default
for act in acts:
if act['type'] == 'DIY_BUY':
act['amountSign'] = 'negative'
# Print transaction details
print(
f" - [{datetime.strptime(act['occurredAt'].replace(':', ''), '%Y-%m-%dT%H%M%S.%f%z')}] [{act['canonicalId']}] {act['description']} "
f"{'+' if act['amountSign'] == 'positive' else '-'}{act['amount']} {act['currency']}")
if act['description'] == f"{act['type']}: {act['subType']}":
# This is an "unknown" transaction, for which description is generic; please open an issue on https://github.com/gboudreau/ws-api-python/issues and include the following:
print(f" Unknown activity: {act}")
print()
if __name__ == "__main__":
WSApiTest().main()
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ws_api-0.30.1.tar.gz.
File metadata
- Download URL: ws_api-0.30.1.tar.gz
- Upload date:
- Size: 77.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae31608abe40ef6c9a54ed329a69dd238ffa7d958b0d8b114548fb8eca15712d
|
|
| MD5 |
a06b200419a4a3a270bd6a70959efb40
|
|
| BLAKE2b-256 |
1b0eca02ee1d14532e153e76a77c10c91dff9c3e499663210861086c4abeb755
|
Provenance
The following attestation bundles were made for ws_api-0.30.1.tar.gz:
Publisher:
publish.yml on gboudreau/ws-api-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ws_api-0.30.1.tar.gz -
Subject digest:
ae31608abe40ef6c9a54ed329a69dd238ffa7d958b0d8b114548fb8eca15712d - Sigstore transparency entry: 849992726
- Sigstore integration time:
-
Permalink:
gboudreau/ws-api-python@3e41290844a5a35465f65eeb0b9128e6d2d81b48 -
Branch / Tag:
refs/tags/0.30.1 - Owner: https://github.com/gboudreau
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3e41290844a5a35465f65eeb0b9128e6d2d81b48 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ws_api-0.30.1-py3-none-any.whl.
File metadata
- Download URL: ws_api-0.30.1-py3-none-any.whl
- Upload date:
- Size: 44.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
265e01741d1fd95a81d54094b1bab306c27e4b0ad9c5c734d05d19f969b1c4bd
|
|
| MD5 |
11fef02786e33e8fdc72db485b0e7c04
|
|
| BLAKE2b-256 |
d8ea5652842f5410a8ac8e210d1d13be3c2e845b745e9c650c14acafc97dfc77
|
Provenance
The following attestation bundles were made for ws_api-0.30.1-py3-none-any.whl:
Publisher:
publish.yml on gboudreau/ws-api-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ws_api-0.30.1-py3-none-any.whl -
Subject digest:
265e01741d1fd95a81d54094b1bab306c27e4b0ad9c5c734d05d19f969b1c4bd - Sigstore transparency entry: 849992727
- Sigstore integration time:
-
Permalink:
gboudreau/ws-api-python@3e41290844a5a35465f65eeb0b9128e6d2d81b48 -
Branch / Tag:
refs/tags/0.30.1 - Owner: https://github.com/gboudreau
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3e41290844a5a35465f65eeb0b9128e6d2d81b48 -
Trigger Event:
release
-
Statement type: