diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000..ba0ad73 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 7d36cd01a7d2d5c6721ae0b96691e81f +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 0000000..c662847 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,109 @@ + + +
+ + +
+"""Core module for Questrade API wrapper."""
+
+import logging
+from typing import Any, Dict, List, Optional, Union
+
+import requests
+import yaml
+
+from .utility import TokenDict, get_access_token_yaml, validate_access_token
+
+log = logging.getLogger(__name__) # pylint: disable=C0103
+
+TOKEN_URL = "https://login.questrade.com/oauth2/token?grant_type=refresh_token&refresh_token="
+
+
+
+[docs]
+class Questrade:
+ """Questrade baseclass.
+
+ This class holds the methods to get access tokens, refresh access tokens as well as get
+ stock quotes and portfolio overview. An instance of the class needs to be either initialized
+ with an access_code or the path of a access token yaml file.
+
+ Parameters
+ ----------
+ access_code: str, optional
+ Access code from Questrade
+ token_yaml: str, optional
+ Path of the yaml-file holding the token payload
+ save_yaml: bool, optional
+ Boolean to indicate if the token payload will be saved in a yaml-file. Default True.
+ """
+
+ def __init__(
+ self,
+ access_code: Optional[str] = None,
+ token_yaml: Optional[str] = None,
+ save_yaml: bool = True,
+ ):
+ self.access_token: TokenDict
+ self.headers = None
+ self.session = requests.Session()
+
+ self.access_code = access_code
+ self.token_yaml = token_yaml
+
+ if access_code is None and self.token_yaml is not None:
+ self.access_token = get_access_token_yaml(self.token_yaml)
+ self.headers = {
+ "Authorization": self.access_token["token_type"]
+ + " "
+ + self.access_token["access_token"]
+ }
+ # add headers to session
+ self.session.headers.update(self.headers)
+ else:
+ self._get_access_token(save_yaml=save_yaml)
+
+ self.account_id = None
+ self.positions = None
+
+ def _send_message(
+ self,
+ method: str,
+ endpoint: str,
+ params: Optional[Dict] = None,
+ data: Optional[Dict] = None,
+ json: Optional[Dict] = None,
+ ) -> Dict[str, Any]: # pylint: disable=R0913
+ """Send an API request.
+
+ Parameters
+ ----------
+ method: str
+ HTTP method (get, post, delete, etc.)
+ endpoint: str
+ Endpoint (to be added to base URL)
+ params: dict, optional
+ HTTP request parameters
+ data: dict, optional
+ JSON-encoded string payload for POST
+ json: dict, optional
+ Dictionary payload for POST
+
+ Returns
+ -------
+ dict/list:
+ JSON response
+ """
+ if self.access_token is not None:
+ url = self.access_token["api_server"] + "/v1/" + endpoint
+ else:
+ log.error("Access token not set...")
+ raise Exception("Access token not set...")
+ resp = self.session.request(method, url, params=params, data=data, json=json, timeout=30)
+ resp.raise_for_status()
+ return resp.json()
+
+
+[docs]
+ def save_token_to_yaml(self, yaml_path: str = "access_token.yml"):
+ """Save the token payload as a yaml-file.
+
+ Parameters
+ ----------
+ yaml_path: str, optional
+ Path of the yaml-file. If the file already exists, it will be overwritten. Defaults to
+ access_token.yml
+ """
+ with open(yaml_path, "w") as yaml_file:
+ log.debug("Saving access token to yaml file...")
+ yaml.dump(self.access_token, yaml_file)
+
+
+ def _get_access_token(
+ self, save_yaml: bool = False, yaml_path: str = "access_token.yml"
+ ) -> TokenDict:
+ """Get access token.
+
+ This internal method gets the access token from the access code and optionally saves it in
+ access_token.yaml.
+
+ Parameters
+ ----------
+ save_yaml: bool, optional
+ Boolean to indicate if the token payload will be saved in a yaml-file. Default False.
+ yaml_path: str, optional
+ Path of the yaml-file that will be saved. If the file already exists, it will be
+ overwritten. Defaults to access_token.yml
+
+ Returns
+ -------
+ dict
+ Dict with the access token data.
+ """
+ url = TOKEN_URL + str(self.access_code)
+ log.info("Getting access token...")
+ data = requests.get(url)
+ data.raise_for_status()
+ response = data.json()
+
+ # validate response
+ validate_access_token(**response)
+
+ self.access_token = response
+
+ # clean the api_server entry of the escape characters
+ self.access_token["api_server"] = self.access_token["api_server"].replace("\\", "")
+ if self.access_token["api_server"][-1] == "/":
+ self.access_token["api_server"] = self.access_token["api_server"][:-1]
+
+ # set headers
+ self.headers = {
+ "Authorization": self.access_token["token_type"]
+ + " "
+ + self.access_token["access_token"]
+ }
+
+ self.session.headers.update(self.headers)
+
+ # save access token
+ if save_yaml:
+ log.info("Saving yaml file to {}...".format(yaml_path)) # pylint: disable=W1202
+ self.save_token_to_yaml(yaml_path=yaml_path)
+
+ return self.access_token
+
+
+[docs]
+ def refresh_access_token(
+ self, from_yaml: bool = False, yaml_path: str = "access_token.yml"
+ ) -> TokenDict:
+ """Refresh access token.
+
+ This method refreshes the access token. This only works if the overall access has not yet
+ expired. By default it will look for the yaml-file, but it could also look for the internal
+ state
+
+ Parameters
+ ----------
+ from_yaml: bool, optional [False]
+ This parameter controls if the refresh token is sourced from a yaml file
+ or if the attribute `access_token` is used (default). If True, the yaml-file will be
+ updated.
+ yaml_path: str, optional
+ Path of the yaml-file that will be updated. Defaults to access_token.yml
+
+ Returns
+ -------
+ dict
+ Dict with the access token data.
+ """
+ if from_yaml:
+ old_access_token = get_access_token_yaml(yaml_path)
+ else:
+ old_access_token = self.access_token
+
+ url = TOKEN_URL + str(old_access_token["refresh_token"])
+ log.info("Refreshing access token...")
+ data = requests.get(url)
+ data.raise_for_status()
+ response = data.json()
+
+ # validate response
+ validate_access_token(**response)
+ # set access token
+ self.access_token = response
+
+ # clean the api_server entry of the escape characters
+ self.access_token["api_server"] = self.access_token["api_server"].replace("\\", "")
+ if self.access_token["api_server"][-1] == "/":
+ self.access_token["api_server"] = self.access_token["api_server"][:-1]
+
+ # set headers
+ self.headers = {
+ "Authorization": self.access_token["token_type"]
+ + " "
+ + self.access_token["access_token"]
+ }
+
+ # update headers
+ self.session.headers.update(self.headers)
+
+ # save access token
+ if from_yaml:
+ self.save_token_to_yaml(yaml_path=yaml_path)
+
+ return self.access_token
+
+
+
+[docs]
+ def get_account_id(self) -> List[int]:
+ """Get account ID.
+
+ This method gets the accounts ID connected to the token.
+
+ Returns
+ -------
+ list:
+ List of account IDs.
+ """
+ log.info("Getting account ID...")
+ response: Dict[str, List[Dict[str, int]]] = self._send_message("get", "accounts")
+
+ account_id = []
+ try:
+ for account in response["accounts"]:
+ account_id.append(account["number"])
+ except Exception:
+ log.error(response)
+ raise Exception
+
+ self.account_id = account_id # type: ignore
+
+ return account_id
+
+
+
+[docs]
+ def get_account_positions(self, account_id: int) -> List[Dict]:
+ """Get account positions.
+
+ This method will get the positions for the account ID connected to the token.
+
+ The returned data is a list where for each position, a dictionary with the following
+ data will be returned:
+
+ .. code-block:: python
+
+ {'averageEntryPrice': 1000,
+ 'closedPnl': 0,
+ 'closedQuantity': 0,
+ 'currentMarketValue': 3120,
+ 'currentPrice': 1040,
+ 'isRealTime': False,
+ 'isUnderReorg': False,
+ 'openPnl': 120,
+ 'openQuantity': 3,
+ 'symbol': 'XYZ',
+ 'symbolId': 1234567,
+ 'totalCost': 3000}
+
+
+ Parameters
+ ----------
+ account_id: int
+ Account ID for which the positions will be returned.
+
+ Returns
+ -------
+ list:
+ List of dictionaries, where each list entry is a dictionary with basic position
+ information.
+
+ """
+ log.info("Getting account positions...")
+ response = self._send_message("get", "accounts/" + str(account_id) + "/positions")
+ try:
+ positions = response["positions"]
+ except Exception:
+ print(response)
+ raise Exception
+
+ self.positions = positions
+
+ return positions
+
+
+
+[docs]
+ def get_account_balances(self, account_id: int) -> Dict:
+ """Get account balances.
+
+ This method will get the account balance for a given account ID.
+
+ Parameters
+ ----------
+ account_id: int
+ Accound ID for which the activities will be returned.
+
+ Returns
+ -------
+ dict:
+ Dictionary holding balance information
+ """
+ log.info("Getting account activities...")
+ response = self._send_message("get", "accounts/" + str(account_id) + "/balances")
+ try:
+ return response
+ except Exception:
+ print(response)
+ raise Exception
+
+
+
+[docs]
+ def get_account_activities(self, account_id: int, start_date: str, end_date: str) -> List[Dict]:
+ """Get account activities.
+
+ This method will get the account activities for a given account ID in a given time
+ interval.
+
+ This method will in general return a list of dictionaries, where each dictionary represents
+ one trade/account activity. Each dictionary is of the form
+
+ .. code-block:: python
+
+ {'action': 'Buy',
+ 'commission': -5.01,
+ 'currency': 'CAD',
+ 'description': 'description text',
+ 'grossAmount': -1000,
+ 'netAmount': -1005.01,
+ 'price': 10,
+ 'quantity': 100,
+ 'settlementDate': '2018-08-09T00:00:00.000000-04:00',
+ 'symbol': 'XYZ.TO',
+ 'symbolId': 1234567,
+ 'tradeDate': '2018-08-07T00:00:00.000000-04:00',
+ 'transactionDate': '2018-08-09T00:00:00.000000-04:00',
+ 'type': 'Trades'}
+
+ Parameters
+ ----------
+ account_id: int
+ Accound ID for which the activities will be returned.
+ startDate: str
+ Start date of time period, format YYYY-MM-DD
+ endDate: str
+ End date of time period, format YYYY-MM-DD
+
+ Returns
+ -------
+ list:
+ List of dictionaries, where each list entry is a dictionary with basic order & dividend
+ information.
+
+ """
+ payload = {
+ "startTime": str(start_date) + "T00:00:00-05:00",
+ "endTime": str(end_date) + "T00:00:00-05:00",
+ }
+
+ log.info("Getting account activities...")
+ response = self._send_message(
+ "get", "accounts/" + str(account_id) + "/activities", params=payload
+ )
+
+ try:
+ activities = response["activities"]
+ except Exception:
+ print(response)
+ raise Exception
+
+ return activities
+
+
+
+[docs]
+ def get_account_executions(self, account_id: int, start_date: str, end_date: str) -> List[Dict]:
+ """Get account executions.
+
+ This method will get the account executionss for a given account ID in a given time
+ interval.
+
+ This method will in general return a list of dictionaries, where each dictionary represents
+ one account execution. Each dictionary is of the form
+
+ .. code-block:: python
+
+
+ {"symbol": "AAPL",
+ "symbolId": 8049,
+ "quantity": 10,
+ "side": "Buy",
+ "price": 536.87,
+ "id": 53817310,
+ "orderId": 177106005,
+ "orderChainId": 17710600,
+ "exchangeExecId": "XS1771060050147",
+ "timestam": 2014-03-31T13:38:29.000000-04:00,
+ "notes": "",
+ "venue": "LAMP",
+ "totalCost": 5368.7,
+ "orderPlacementCommission": 0,
+ "commission": 4.95,
+ "executionFee": 0,
+ "secFee": 0,
+ "canadianExecutionFee": 0,
+ "parentId": 0,
+ }
+
+ Parameters
+ ----------
+ account_id: int
+ Accound ID for which the executionss will be returned.
+ startDate: str
+ Start date of time period, format YYYY-MM-DD
+ endDate: str
+ End date of time period, format YYYY-MM-DD
+
+ Returns
+ -------
+ list:
+ List of dictionaries, where each list entry is a dictionary with execution
+ information.
+
+ """
+ payload = {
+ "startTime": str(start_date) + "T00:00:00-05:00",
+ "endTime": str(end_date) + "T00:00:00-05:00",
+ }
+
+ log.info("Getting account executions...")
+ response = self._send_message(
+ "get", "accounts/" + str(account_id) + "/executions", params=payload
+ )
+
+ try:
+ executions = response["executions"]
+ except Exception:
+ print(response)
+ raise Exception
+
+ return executions
+
+
+
+[docs]
+ def ticker_information(self, tickers: Union[str, List[str]]) -> Union[Dict, List[Dict]]:
+ """Get ticker information.
+
+ This function gets information such as a quote for a single ticker or a list of tickers.
+
+ Parameters
+ ----------
+ tickers: str or [str]
+ List of tickers or a single ticker
+
+ Returns
+ -------
+ dict or [dict]
+ Dictionary with ticker information or list of dictionaries with ticker information
+ """
+ if isinstance(tickers, str):
+ tickers = [tickers]
+
+ payload = {"names": ",".join(tickers)}
+
+ log.info("Getting ticker data...")
+ response = self._send_message("get", "symbols", params=payload)
+ try:
+ symbols = response["symbols"]
+ except Exception:
+ print(response)
+ raise Exception
+
+ if len(tickers) == 1:
+ symbols = symbols[0]
+
+ return symbols
+
+
+
+[docs]
+ def get_quote(self, tickers: List[str]) -> Union[Dict, List[Dict]]:
+ """Get quote.
+
+ This function gets information such as a quote for a single ticker or a list of tickers.
+
+ Parameters
+ ----------
+ tickers: [str]
+ List of tickers
+
+ Returns
+ -------
+ dict or [dict]
+ Dictionary with quotes or list of dictionaries with quotes
+ """
+ if isinstance(tickers, str):
+ tickers = [tickers]
+
+ # translate tickers to IDs
+ info = self.ticker_information(tickers)
+ if len(tickers) == 1 and isinstance(info, dict):
+ ids = [info["symbolId"]]
+ else:
+ ids = [stock["symbolId"] for stock in info]
+
+ payload = {"ids": ",".join(map(str, ids))}
+
+ log.info("Getting quote...")
+ response = self._send_message("get", "markets/quotes", params=payload)
+ try:
+ quotes = response["quotes"]
+ except Exception:
+ print(response)
+ raise Exception
+
+ if len(ids) == 1:
+ quotes = quotes[0]
+
+ return quotes
+
+
+
+[docs]
+ def get_historical_data(
+ self, ticker: str, start_date: str, end_date: str, interval: str
+ ) -> List:
+ """Get historical ticker data.
+
+ This method get gets historical data for a time interval and a defined time frequency.
+
+ Parameters
+ ----------
+ ticker: str
+ Ticker Symbol
+ start_date: str
+ Date in the format YYYY-MM-DD
+ end_date: str
+ Date in the format YYYY-MM-DD
+ interval: str
+ Time frequency, i.e. OneDay.
+
+ Returns
+ -------
+ list:
+ list of historical data for each interval. The list is ordered by date.
+ """
+ # translate tickers to IDs
+ info = self.ticker_information(ticker)
+ if isinstance(info, dict):
+ ids = info["symbolId"]
+ else:
+ log.error(f"Something went wrong retrieving the symbol ID for ticker {ticker}...")
+ raise Exception(f"Something went wrong retrieving the symbol ID for ticker {ticker}...")
+
+ if interval not in self._valid_intervals():
+ log.error(f"{interval} not a valid interval option.")
+ raise Exception(f"{interval} must be one of {list(self._valid_intervals())}")
+
+ payload = {
+ "startTime": str(start_date) + "T00:00:00-05:00",
+ "endTime": str(end_date) + "T00:00:00-05:00",
+ "interval": str(interval),
+ }
+
+ log.info(
+ "Getting historical data for {0} from {1} to {2}".format(ticker, start_date, end_date)
+ )
+
+ response = self._send_message("get", "markets/candles/" + str(ids), params=payload)
+ try:
+ quotes = response["candles"]
+ except Exception:
+ print(response)
+ raise Exception
+
+ return quotes
+
+
+
+[docs]
+ def submit_order(self, acct_id: int, order_dict: Dict[str, Union[int, bool, str]]) -> Dict:
+ """Submit order.
+
+ This method submits an order to Questrade. Note that currently only partner apps can submit
+ orders to the Questrade API. The order information is provided in a dictionary of the form
+
+ .. code-block:: python
+
+ {'accountNumber': 1234567,
+ 'symbolId': 3925293,
+ 'quantity': 1,
+ 'icebergQuantity': 1,
+ 'limitPrice': 57.58,
+ 'isAllOrNone': True,
+ 'isAnonymous': False,
+ 'orderType': 'Limit',
+ 'timeInForce': 'GoodTillCanceled',
+ 'action': 'Buy',
+ 'primaryRoute': 'AUTO',
+ 'secondaryRoute': 'AUTO'}
+
+ Parameters
+ ----------
+ acct_id: int
+ Account ID for the account to which the order is to be submitted.
+ order_dict: dict
+ Dictionary with the necessary order entries.
+
+ Returns
+ -------
+ dict
+ Dictionary with the API response to the order submission.
+ """
+ uri = self.access_token["api_server"] + "/v1/accounts/" + str(acct_id) + "/orders"
+ log.info("Posting order...")
+ data = self.session.post(uri, json=order_dict)
+ data.raise_for_status()
+ response = self._send_message(
+ "post", "accounts/" + str(acct_id) + "/orders", json=order_dict
+ )
+
+ return response
+
+
+
+[docs]
+ def get_option_chain(self, ticker: str) -> Dict:
+ """Retrieve an option chain for a particular underlying symbol.
+
+ https://www.questrade.com/api/documentation/rest-operations/market-calls/symbols-id-options
+
+ This method will return a dictionary of the form
+
+ .. code-block:: python
+
+ {
+ "options": [
+ {
+ "expiryDate": "2015-01-17T00:00:00.000000-05:00",
+ "description": "BANK OF MONTREAL",
+ "listingExchange": "MX",
+ "optionExerciseType": "American",
+ "chainPerRoot": [
+ {
+ "root": "BMO",
+ "chainPerStrikePrice": [
+ {
+ "strikePrice": 60,
+ "callSymbolId": 6101993,
+ "putSymbolId": 6102009
+ },
+ {
+ "strikePrice": 62,
+ "callSymbolId": 6101994,
+ "putSymbolId": 6102010
+ },
+ {
+ "strikePrice": 64,
+ "callSymbolId": 6101995,
+ "putSymbolId": 6102011
+ }],
+ "multiplier": 100
+ }]
+ }]
+ }
+
+ Parameters
+ ----------
+ ticker: str
+ Ticker symbol
+
+ Returns
+ -------
+ dict:
+ Dictionary of option chain information for a particular symbol.
+ """
+ log.info(f"Getting option chain for ticker {ticker} ...")
+ info = self.ticker_information([ticker])
+ if not isinstance(info, dict):
+ log.error(f"Something went wrong retrieving the symbol ID for ticker {ticker}...")
+ raise Exception(f"Something went wrong retrieving the symbol ID for ticker {ticker}...")
+ symbol_id = info["symbolId"]
+ response = self._send_message("get", "symbols/" + str(symbol_id) + "/options")
+ return response
+
+
+
+[docs]
+ def get_option_quotes(self, filters: List[Dict], option_ids: List[int]) -> Dict:
+ """Retrieve a single Level 1 market quote and Greek data for one or more option symbols.
+
+ www.questrade.com/api/documentation/rest-operations/market-calls/markets-quotes-options
+
+ This method will return a dictionary of the form
+
+ .. code-block:: python
+
+ {"optionQuotes": [
+ {
+ "underlying": "MSFT",
+ "underlyingId": 27426,
+ "symbol": "MSFT20Jan17C70.00",
+ "symbolId": 7413503,
+ "bidPrice": 4.90,
+ "bidSize": 0,
+ "askPrice": 4.95,
+ "askSize": 0,
+ "lastTradePriceTrHrs": 4.93,
+ "lastTradePrice": 4.93,
+ "lastTradeSize": 0,
+ "lastTradeTick": "Equal",
+ "lastTradeTime": "2015-08-17T00:00:00.000000-04:00",
+ "volume": 0,
+ "openPrice": 0,
+ "highPricehighPrice": 4.93,
+ "lowPrice": 0,
+ "volatility": 52.374257,
+ "delta": 0.06985,
+ "gamma": 0.01038,
+ "theta": -0.001406,
+ "vega": 0.074554,
+ "rho": 0.04153,
+ "openInterest": 2292,
+ "delay": 0,
+ "isHalted": False,
+ "VWAP": 0,
+ }
+ ]
+ }
+
+ Parameters
+ ----------
+ filters: List of dictionaries
+ List of filters. For example
+
+ .. code-block:: python
+
+ [
+ {
+ "optionType": "Call",
+ "underlyingId": 27426,
+ "expiryDate": "2017-01-20T00:00:00.000000-05:00",
+ "minstrikePrice": 70,
+ "maxstrikePrice": 80
+ }
+ ]
+
+ option_ids: [int]
+ List of option IDs
+
+ Returns
+ -------
+ dict:
+ Dictionary of option quotes.
+ """
+ log.info(
+ "Getting option quotes for filter {0} and option_ids {1} ...".format(
+ filters, option_ids
+ )
+ )
+ payload = dict()
+ if filters is not None:
+ payload["filters"] = filters
+ if option_ids is not None:
+ payload["optionIds"] = option_ids
+ response = self._send_message("post", "markets/quotes/options", json=payload)
+ return response
+
+
+ def __del__(self):
+ """Close session when class instance is deleted."""
+ self.session.close()
+
+ @staticmethod
+ def _valid_intervals():
+ return set(["OneDay", "OneWeek", "OneMonth", "OneYear"])
+
+
+"""Collection of utility functions."""
+import logging
+import sys
+from typing import Optional
+
+import yaml
+
+if sys.version_info < (3, 8, 0):
+ from typing_extensions import TypedDict
+else:
+ from typing import ( # type: ignore ## needed to do this for mypy error in python < 3.8
+ TypedDict,
+ )
+
+
+log = logging.getLogger(__name__) # pylint: disable=C0103
+
+TokenDict = TypedDict(
+ "TokenDict",
+ {
+ "access_token": str,
+ "api_server": str,
+ "expires_in": int,
+ "refresh_token": str,
+ "token_type": str,
+ },
+)
+
+
+
+[docs]
+def get_access_token_yaml(token_yaml: str) -> TokenDict:
+ """Read in access token yaml.
+
+ Parameters
+ ----------
+ token_yaml: str
+ Path of the token yaml file
+
+ Returns
+ -------
+ dict
+ Dicitonary with the access token parameters
+ """
+ try:
+ with open(token_yaml) as yaml_file:
+ log.debug("Loading access token from yaml...")
+ token_yaml_payload: TokenDict = yaml.load(yaml_file, Loader=yaml.FullLoader)
+ except Exception:
+ log.error("Error loading access token from yaml...")
+ raise
+
+ validate_access_token(**token_yaml_payload)
+ return token_yaml_payload
+
+
+
+
+[docs]
+def validate_access_token(
+ access_token: Optional[str] = None,
+ api_server: Optional[str] = None,
+ expires_in: Optional[int] = None,
+ refresh_token: Optional[str] = None,
+ token_type: Optional[str] = None,
+):
+ """Validate access token.
+
+ This function validates the access token and ensures that all requiered
+ attributes are provided.
+
+ Parameters
+ ----------
+ access_token: str, optional
+ Access token
+ api_server: str, optional
+ Api server URL
+ expires_in: int, optional
+ Time until token expires
+ refresh_token: str, optional
+ Refresh token
+ token_type: str, optional
+ Token type
+
+ Raises
+ ------
+ Exception
+ If any of the inputs is None.
+ """
+ log.debug("Validating access token...")
+ if access_token is None:
+ raise Exception("Access token was not provided.")
+ if api_server is None:
+ raise Exception("API server URL was not provided.")
+ if expires_in is None:
+ raise Exception("Expiry time was not provided.")
+ if refresh_token is None:
+ raise Exception("Refresh token was not provided.")
+ if token_type is None:
+ raise Exception("Token type was not provided.")
+
+
' + + '' + + _("Hide Search Matches") + + "
" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/api/modules.html b/api/modules.html new file mode 100644 index 0000000..22f2a93 --- /dev/null +++ b/api/modules.html @@ -0,0 +1,156 @@ + + + + + + +Questrade
Questrade.get_account_activities()
Questrade.get_account_balances()
Questrade.get_account_executions()
Questrade.get_account_id()
Questrade.get_account_positions()
Questrade.get_historical_data()
Questrade.get_option_chain()
Questrade.get_option_quotes()
Questrade.get_quote()
Questrade.refresh_access_token()
Questrade.save_token_to_yaml()
Questrade.submit_order()
Questrade.ticker_information()
TokenDict
+get_access_token_yaml()
validate_access_token()
Core module for Questrade API wrapper.
+Bases: object
Questrade baseclass.
+This class holds the methods to get access tokens, refresh access tokens as well as get +stock quotes and portfolio overview. An instance of the class needs to be either initialized +with an access_code or the path of a access token yaml file.
+access_code (str, optional) – Access code from Questrade
token_yaml (str, optional) – Path of the yaml-file holding the token payload
save_yaml (bool, optional) – Boolean to indicate if the token payload will be saved in a yaml-file. Default True.
Get account activities.
+This method will get the account activities for a given account ID in a given time +interval.
+This method will in general return a list of dictionaries, where each dictionary represents +one trade/account activity. Each dictionary is of the form
+{'action': 'Buy',
+'commission': -5.01,
+'currency': 'CAD',
+'description': 'description text',
+'grossAmount': -1000,
+'netAmount': -1005.01,
+'price': 10,
+'quantity': 100,
+'settlementDate': '2018-08-09T00:00:00.000000-04:00',
+'symbol': 'XYZ.TO',
+'symbolId': 1234567,
+'tradeDate': '2018-08-07T00:00:00.000000-04:00',
+'transactionDate': '2018-08-09T00:00:00.000000-04:00',
+'type': 'Trades'}
+
account_id (int) – Accound ID for which the activities will be returned.
startDate (str) – Start date of time period, format YYYY-MM-DD
endDate (str) – End date of time period, format YYYY-MM-DD
List of dictionaries, where each list entry is a dictionary with basic order & dividend +information.
+list
+Get account balances.
+This method will get the account balance for a given account ID.
+account_id (int) – Accound ID for which the activities will be returned.
+Dictionary holding balance information
+dict
+Get account executions.
+This method will get the account executionss for a given account ID in a given time +interval.
+This method will in general return a list of dictionaries, where each dictionary represents +one account execution. Each dictionary is of the form
+ {"symbol": "AAPL",
+ "symbolId": 8049,
+ "quantity": 10,
+ "side": "Buy",
+ "price": 536.87,
+ "id": 53817310,
+ "orderId": 177106005,
+ "orderChainId": 17710600,
+ "exchangeExecId": "XS1771060050147",
+ "timestam": 2014-03-31T13:38:29.000000-04:00,
+ "notes": "",
+ "venue": "LAMP",
+ "totalCost": 5368.7,
+ "orderPlacementCommission": 0,
+ "commission": 4.95,
+ "executionFee": 0,
+ "secFee": 0,
+ "canadianExecutionFee": 0,
+ "parentId": 0,
+}
+
account_id (int) – Accound ID for which the executionss will be returned.
startDate (str) – Start date of time period, format YYYY-MM-DD
endDate (str) – End date of time period, format YYYY-MM-DD
List of dictionaries, where each list entry is a dictionary with execution +information.
+list
+Get account ID.
+This method gets the accounts ID connected to the token.
+List of account IDs.
+list
+Get account positions.
+This method will get the positions for the account ID connected to the token.
+The returned data is a list where for each position, a dictionary with the following +data will be returned:
+{'averageEntryPrice': 1000,
+'closedPnl': 0,
+'closedQuantity': 0,
+'currentMarketValue': 3120,
+'currentPrice': 1040,
+'isRealTime': False,
+'isUnderReorg': False,
+'openPnl': 120,
+'openQuantity': 3,
+'symbol': 'XYZ',
+'symbolId': 1234567,
+'totalCost': 3000}
+
account_id (int) – Account ID for which the positions will be returned.
+List of dictionaries, where each list entry is a dictionary with basic position +information.
+list
+Get historical ticker data.
+This method get gets historical data for a time interval and a defined time frequency.
+ticker (str) – Ticker Symbol
start_date (str) – Date in the format YYYY-MM-DD
end_date (str) – Date in the format YYYY-MM-DD
interval (str) – Time frequency, i.e. OneDay.
list of historical data for each interval. The list is ordered by date.
+list
+Retrieve an option chain for a particular underlying symbol.
+https://www.questrade.com/api/documentation/rest-operations/market-calls/symbols-id-options
+This method will return a dictionary of the form
+{
+ "options": [
+ {
+ "expiryDate": "2015-01-17T00:00:00.000000-05:00",
+ "description": "BANK OF MONTREAL",
+ "listingExchange": "MX",
+ "optionExerciseType": "American",
+ "chainPerRoot": [
+ {
+ "root": "BMO",
+ "chainPerStrikePrice": [
+ {
+ "strikePrice": 60,
+ "callSymbolId": 6101993,
+ "putSymbolId": 6102009
+ },
+ {
+ "strikePrice": 62,
+ "callSymbolId": 6101994,
+ "putSymbolId": 6102010
+ },
+ {
+ "strikePrice": 64,
+ "callSymbolId": 6101995,
+ "putSymbolId": 6102011
+ }],
+ "multiplier": 100
+ }]
+ }]
+}
+
ticker (str) – Ticker symbol
+Dictionary of option chain information for a particular symbol.
+dict
+Retrieve a single Level 1 market quote and Greek data for one or more option symbols.
+www.questrade.com/api/documentation/rest-operations/market-calls/markets-quotes-options
+This method will return a dictionary of the form
+{"optionQuotes": [
+ {
+ "underlying": "MSFT",
+ "underlyingId": 27426,
+ "symbol": "MSFT20Jan17C70.00",
+ "symbolId": 7413503,
+ "bidPrice": 4.90,
+ "bidSize": 0,
+ "askPrice": 4.95,
+ "askSize": 0,
+ "lastTradePriceTrHrs": 4.93,
+ "lastTradePrice": 4.93,
+ "lastTradeSize": 0,
+ "lastTradeTick": "Equal",
+ "lastTradeTime": "2015-08-17T00:00:00.000000-04:00",
+ "volume": 0,
+ "openPrice": 0,
+ "highPricehighPrice": 4.93,
+ "lowPrice": 0,
+ "volatility": 52.374257,
+ "delta": 0.06985,
+ "gamma": 0.01038,
+ "theta": -0.001406,
+ "vega": 0.074554,
+ "rho": 0.04153,
+ "openInterest": 2292,
+ "delay": 0,
+ "isHalted": False,
+ "VWAP": 0,
+ }
+ ]
+}
+
filters (List of dictionaries) – List of filters. For example
+++++[ + { + "optionType": "Call", + "underlyingId": 27426, + "expiryDate": "2017-01-20T00:00:00.000000-05:00", + "minstrikePrice": 70, + "maxstrikePrice": 80 + } + ] +
List of option IDs
+Dictionary of option quotes.
+dict
+Get quote.
+This function gets information such as a quote for a single ticker or a list of tickers.
+tickers ([str]) – List of tickers
+Dictionary with quotes or list of dictionaries with quotes
+dict or [dict]
+Refresh access token.
+This method refreshes the access token. This only works if the overall access has not yet +expired. By default it will look for the yaml-file, but it could also look for the internal +state
+from_yaml (bool, optional [False]) – This parameter controls if the refresh token is sourced from a yaml file +or if the attribute access_token is used (default). If True, the yaml-file will be +updated.
yaml_path (str, optional) – Path of the yaml-file that will be updated. Defaults to access_token.yml
Dict with the access token data.
+dict
+Save the token payload as a yaml-file.
+yaml_path (str, optional) – Path of the yaml-file. If the file already exists, it will be overwritten. Defaults to +access_token.yml
+Submit order.
+This method submits an order to Questrade. Note that currently only partner apps can submit +orders to the Questrade API. The order information is provided in a dictionary of the form
+{'accountNumber': 1234567,
+'symbolId': 3925293,
+'quantity': 1,
+'icebergQuantity': 1,
+'limitPrice': 57.58,
+'isAllOrNone': True,
+'isAnonymous': False,
+'orderType': 'Limit',
+'timeInForce': 'GoodTillCanceled',
+'action': 'Buy',
+'primaryRoute': 'AUTO',
+'secondaryRoute': 'AUTO'}
+
acct_id (int) – Account ID for the account to which the order is to be submitted.
order_dict (dict) – Dictionary with the necessary order entries.
Dictionary with the API response to the order submission.
+dict
+Get ticker information.
+This function gets information such as a quote for a single ticker or a list of tickers.
+tickers (str or [str]) – List of tickers or a single ticker
+Dictionary with ticker information or list of dictionaries with ticker information
+dict or [dict]
+Collection of utility functions.
+Bases: TypedDict
Read in access token yaml.
+token_yaml (str) – Path of the token yaml file
+Dicitonary with the access token parameters
+dict
+Validate access token.
+This function validates the access token and ensures that all requiered +attributes are provided.
+access_token (str, optional) – Access token
api_server (str, optional) – Api server URL
expires_in (int, optional) – Time until token expires
refresh_token (str, optional) – Refresh token
token_type (str, optional) – Token type
Exception – If any of the inputs is None.
+module init file
++ | + |
+ |
|
+
+ |
|
+
+ | + |
+ | + |
+ | + |
+ |
+ q | ||
+ |
+ qtrade | + |
+ |
+ qtrade.questrade | + |
+ |
+ qtrade.utility | + |
This is a very basic Python 3 wrapper for the +Questrade API, a Canadian low cost +broker.
+This package is available via PyPI and can be installed via +the command
+pip install qtrade
+
The main class of the package is called Questrade
and houses most of the functionality provided
+by the package. Below are a few examples for possible use cases.
The central class can be initialized via
+from qtrade import Questrade
+
+qtrade = Questrade(access_code='<access_code>')
+
where <access_code>
is the token that one gets from the Questrade API portal. It is called
+access_code
since this initial token is used to get the full token data that will include
{'access_token': <access_token>,
+ 'api_server': '<api_url>',
+ 'expires_in': 1234,
+ 'refresh_token': <refresh_token>,
+ 'token_type': 'Bearer'}
+
The first call initializes the class and the second call gets the full token.
+Another way to initialize the class is to use a token yaml-file via:
+qtrade = Questrade(token_yaml='<yaml_path>')
+
where the yaml-file would have the general form
+..code:: yaml
+++access_token: <access_token> +api_server: <api_url> +expires_in: 1234 +refresh_token: <refresh_token> +token_type: Bearer
+
If the token is expired, one can use
+qtrade.refresh_token()
+
to refresh the access token using the saved refresh token.
+Once the tokens are set correctly, I have currently added methods to get ticker quotes, the +current status of all positions in any Questrade account that is associated with the tokens, +any account activities such as trades and dividend payments as well as historical data for +tickers that are supported by Questrade.
+There currently exists some basic functionality to get stock information via
+aapl, amzn = qtrade.ticker_information(['AAPL', 'AMZN'])
+
and current stock quotes can be obtained via
+aapl_quote, amzn_quote = qtrade.get_quote(['AAPL', 'AMZN'])
+
In addition, one can get historical stock quotes via
+aapl_history = qtrade.get_historical_data('AAPL', '2018-08-01', '2018-08-21','OneHour')
+
Here, the last input parameter is the interval between quotes. Another option could be 'OneDay'
.
+For more options, see the `Questrade API description <http://www.questrade.com/api/documentation/rest-operations/enumerations/enumerations#historical-data-granularity`_.
In addition, the Questrade API gives access to account information about the accounts connected to +the token. The accounts IDs can be accessed via
+account_ids = qtrade.get_account_id()
+
By using the correct account ID, one can get the positions of the accounts via
+positions = qtrade.get_account_positions(account_id=123456)
+
Finally, there exists a method to get all account activities (trades, dividends received, etc.) of +an account in a certain time frame via
+activities = qtrade.get_account_activities(123456, '2018-08-01', '2018-08-16')
+
Contributions are always appreciated! For example:
+open an issue for a missing feature or a bug
give feedback about existing functionality
make suggestions for improvements
submit a PR with a new feature (though reaching out would be appreciated)
etc.
There is a test suite that can be run via python -m pytest
. This project uses pre-commit
+and black
which takes care of automatic code formatting and linting. When setting up the development
+environment, run pre-commit install
to set up the hook. This will run black automatically when
+committing code changes.
I am in no way affiliated with Questrade and using this API wrapper is licensed via the MIT license.
+