diff --git a/README.md b/README.md index 7ca91e5..859fe30 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ## Overview -Current Version: **0.2.9** +Current Version: **0.3.0** The unofficial Python API client library for TD Ameritrade allows individuals with TD Ameritrade accounts to manage trades, pull historical and real-time data, manage their accounts, create and modify orders all using the Python programming language. diff --git a/setup.py b/setup.py index 4e14ff0..214cf0d 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,8 @@ -from setuptools import setup, find_packages +from setuptools import setup +from setuptools import find_packages # load the README file. -with open("README.md", "r") as fh: +with open(file="README.md", mode="r") as fh: long_description = fh.read() setup( @@ -16,7 +17,7 @@ # I'm in alpha development still, so a compliant version number is a1. # read this as MAJOR VERSION 0, MINOR VERSION 1, MAINTENANCE VERSION 0 - version='0.2.9', + version='0.3.0', # here is a simple description of the library, this will appear when # someone searches for the library on https://pypi.org/search diff --git a/td/app/auth.py b/td/app/auth.py index 91ab71e..49f8c9e 100644 --- a/td/app/auth.py +++ b/td/app/auth.py @@ -5,7 +5,7 @@ from configparser import ConfigParser from requests_oauthlib import OAuth2Session from datetime import datetime -from td.defaults import StatePath +from td.utils import StatePath from typing import Union from typing import List diff --git a/td/client.py b/td/client.py index c2c3451..c8a01a7 100644 --- a/td/client.py +++ b/td/client.py @@ -6,25 +6,34 @@ import requests import urllib.parse -from . import defaults - from typing import Any from typing import Dict from typing import List from typing import Union from typing import Optional +from td.utils import StatePath +from td.utils import TDUtilities + from td.orders import Order from td.orders import OrderLeg -from td.defaults import StatePath from td.stream import TDStreamerClient from td.option_chain import OptionChain -from td.fields import VALID_CHART_VALUES -from td.fields import ENDPOINT_ARGUMENTS -from td.app.auth import FlaskTDAuth + +from td.enums import VALID_CHART_VALUES +from td.enums import ENDPOINT_ARGUMENTS + from td.oauth import run from td.oauth import shutdown -from td.exceptions import TknExpError, ExdLmtError, NotNulError, ForbidError, NotFndError, ServerError, GeneralError +from td.app.auth import FlaskTDAuth + +from td.exceptions import TknExpError +from td.exceptions import ExdLmtError +from td.exceptions import NotNulError +from td.exceptions import ForbidError +from td.exceptions import NotFndError +from td.exceptions import ServerError +from td.exceptions import GeneralError class TDClient(): @@ -105,6 +114,7 @@ def __init__(self, client_id: str, redirect_uri: str, account_number: str = None self.account_number = account_number self.credentials_path = StatePath(credentials_file=credentials_path) self._redirect_code = None + self._td_utilities = TDUtilities() if self.auth_flow == 'flask': self._flask_app = FlaskTDAuth( @@ -546,8 +556,8 @@ def _make_request(self, method: str, endpoint: str, mode: str = None, params: di else: order_id = '' + # If it's okay and we need details, then add them. if response.ok and order_details: - response_dict = { 'order_id':order_id, 'headers':response_headers, @@ -559,19 +569,11 @@ def _make_request(self, method: str, endpoint: str, mode: str = None, params: di return response_dict + # If it's okay and no details. elif response.ok: - return response.json() else: - - print('='*80) - print("RESPONSE STATUS CODE: {status_code}".format(status_code=status_code)) - print("RESPONSE URL: {url}".format(url=response.url)) - print("RESPONSE HEADERS: {headers}".format(headers=response.headers)) - print("RESPONSE PARAMS: {params}".format(params=response.links)) - print("RESPONSE TEXT: {text}".format(text=response.text)) - print('-'*80) if response.status_code == 400: raise NotNulError(message=response.text) @@ -745,17 +747,13 @@ def get_price_history(self, symbol: str, period_type:str = None, period: str = N elif (not start_date and not end_date and period): # Attempt to grab the key, if it fails we know there is an error. - try: + # check if the period is valid. + if int(period) in VALID_CHART_VALUES[frequency_type][period_type]: + True + else: + raise IndexError('Invalid Period.') - # check if the period is valid. - if period in VALID_CHART_VALUES[frequency_type][int(period_type)]: - True - else: - raise IndexError('Invalid Period.') - except: - raise KeyError('Invalid Frequency Type or Period Type you passed through is not valid') - - if frequency_type == 'minute' and frequency not in ['1', '5', '10', '15', '30']: + if frequency_type == 'minute' and int(frequency) not in [1, 5, 10, 15, 30]: raise ValueError('Invalid Minute Frequency, must be 1,5,10,15,30') # build the params dictionary @@ -2027,7 +2025,8 @@ def create_streaming_session(self) -> TDStreamerClient: # Grab the Streamer Info. userPrincipalsResponse = self.get_user_principals( - fields=['streamerConnectionInfo','streamerSubscriptionKeys','preferences','surrogateIds']) + fields=['streamerConnectionInfo','streamerSubscriptionKeys','preferences','surrogateIds'] + ) # Grab the timestampe. @@ -2038,7 +2037,8 @@ def create_streaming_session(self) -> TDStreamerClient: # Parse the token timestamp. tokenTimeStampAsMs = self._create_token_timestamp( - token_timestamp=tokenTimeStamp) + token_timestamp=tokenTimeStamp + ) # Define our Credentials Dictionary used for authentication. credentials = { diff --git a/td/defaults.py b/td/defaults.py deleted file mode 100644 index 2bd75ab..0000000 --- a/td/defaults.py +++ /dev/null @@ -1,266 +0,0 @@ -import os -import sys -import json -import pathlib - -from typing import Any -from typing import Dict -from typing import List -from typing import Union -from typing import Optional - -class StatePath(type(pathlib.Path())): - - def __init__(self, credentials_file: str = None): - - """Initalizes the StatePath Class""" - self.python_version = sys.version_info - self.credenitals_file_name = 'td_state.json' - self.settings_location = {} - - if credentials_file and isinstance(credentials_file, str): - self.credentials_file: pathlib.Path = pathlib.Path(credentials_file) - else: - self.credentials_file: pathlib.Path = self.library_directory - - @property - def get_file_path(self): - - return self.credentials_file.absolute() - - def path_home(self) -> pathlib.PurePath: - """Determines the user's Home Path using Pathlib. - - Returns: - ---- - {pathlib.PurePath} -- A PurePath object that points to - the user's home directory. - """ - - home_directory = pathlib.Path.home() - return home_directory - - @property - def home_directory(self) -> pathlib.PurePath: - """Returns the Home directory path. - - Returns: - ---- - {pathlib.PurePath} -- A path object. - """ - return self.path_home() - - @property - def library_directory(self) -> pathlib.PurePath: - """Returns the TD Library directory path. - - Returns: - ---- - {pathlib.PurePath} -- A path object. - """ - return self.path_library() - - @property - def settings_directory(self) -> pathlib.PurePath: - """Returns the `.td_python_library` directory path. - - Returns: - ---- - {pathlib.PurePath} -- A path object. - """ - return self.path_settings() - - def path_library(self) -> pathlib.PurePath: - """Generates the TD Library Path. - - Returns: - ---- - {pathlib.PurePath} -- A PurePath object pointing to the TD - library. - """ - library_directory = pathlib.Path(__file__).parent - return library_directory - - def path_settings(self) -> pathlib.PurePath: - """Generates a path to the `.td_python_library` directory. - - Returns: - ---- - {pathlib.PurePath} -- A PurePath object pointing to the `.td_python_library` - directory. - """ - self.home_directory - settings_directory = self.home_directory.joinpath('.td_python_library') - return settings_directory - - def json_settings_path(self): - """Generates a path to the `.td_python_library/td_state.json` file. - - Returns: - ---- - {pathlib.PurePath} -- A PurePath object pointing to the - `.td_python_library/td_state.json` file. - """ - return self.settings_directory.joinpath(self.credenitals_file_name) - - def json_library_path(self): - """Generates a path to the `td/td_state.json` file. - - Returns: - ---- - {pathlib.PurePath} -- A PurePath object pointing to the - `td/td_state.json` file. - """ - return self.library_directory.joinpath(self.credenitals_file_name) - - @property - def does_credentials_file_exist(self): - """Sepcifies whether the passed through credentials file exists.""" - - return self.credentials_file.exists() - - def does_file_exist(self, file_path: pathlib.Path) -> bool: - """Checks if a file exists. - - Arguments: - ---- - file_path {pathlib.Path} -- A path to a specific file. - - Returns: - ---- - bool -- `True` if it exists, `False` if it does not exist. - """ - return file_path.exists() - - def does_directory_exist(self, file_path: pathlib.Path) -> bool: - """Checks if a directory exists. - - This takes a file path and checks if folder that the file is supposed - to exist in exists. It only does one level up. - - Arguments: - ---- - file_path {pathlib.Path} -- A path to a specific directory. - - Returns: - ---- - bool -- `True` if it exists, `False` if it does not exist. - """ - - if isinstance(file_path, str): - file_path = pathlib.Path(file_path).absolute() - directory = file_path - else: - file_path = file_path.absolute() - directory = file_path.parent - - # See if it exists - return directory.exists() - - def write_to_settings(self, state: dict) -> pathlib.Path: - """Writes the credentials to the Settigns folder. - - Arguments: - ---- - state {dict} -- The session state dictionary. - - Returns: - ---- - pathlib.Path -- The path to credentials path. - """ - - json_settings_path = self.json_settings_path() - - # Check to see if the folder exists. - if not self.does_directory_exist(file_path=json_settings_path): - json_settings_path.parent.mkdir() - - # write to the JSON file. - with open(file=json_settings_path, mode='w+') as credenitals_file: - json.dump(obj=state,fp=credenitals_file) - - return json_settings_path - - def write_credentials(self, file_path: Union[pathlib.Path, str], state: dict) -> pathlib.Path: - """Writes the credentials to the Settigns folder. - - Arguments: - ---- - file_path {Union[pathlib.Path, str]} -- The path to the credentials file. - - state {dict} -- The session state dictionary. - - Returns: - ---- - pathlib.Path -- The path to credentials path. - """ - - if isinstance(file_path, str): - json_path = pathlib.Path(file_path).absolute() - else: - json_path = file_path.absolute() - - # Check to see if the folder exists. - if not self.does_directory_exist(file_path=json_path): - json_path.parent.mkdir() - - # write to the JSON file. - with open(file=json_path, mode='w+') as credenitals_file: - json.dump(obj=state,fp=credenitals_file) - - return json_path - - def read_credentials(self, file_path: Union[pathlib.Path, str]) -> dict: - """Read the credentials file. - - Arguments: - ---- - file_path {Union[pathlib.Path, str]} -- The path to the credentials file. - - Returns: - ---- - {dict} -- The session state dictionary. - """ - - # Handle the file path input. - if isinstance(file_path, str): - json_path = pathlib.Path(file_path).absolute() - else: - json_path = file_path.absolute() - - # Check to see if the folder exists. - if not self.does_directory_exist(file_path=json_path): - raise FileNotFoundError("Credentials File does not exist.") - - # read the JSON file. - with open(file=json_path, mode='r') as credenitals_file: - state_dict = json.load(fp=credenitals_file) - - return state_dict - - def set_path(self, path: str) -> None: - """Sets the path to credentials file. - - Arguments: - ---- - path {str} -- The path to set. - """ - self._credentials_full_path = path - - def delete_credentials(self, file_path: pathlib.Path) -> None: - - file_path.unlink() - - def define_settings_location(self, location_id:str, location: str) -> pathlib.Path: - - new_path = pathlib.Path(location) - - self.settings_location[location_id] = new_path - - return new_path - - -if __name__ == '__main__': - - state_path = StatePath() - state_path.write_to_settings(state={'value':'key'}) diff --git a/td/enums.py b/td/enums.py index 0562a8f..360efaf 100644 --- a/td/enums.py +++ b/td/enums.py @@ -299,3 +299,706 @@ class OPTION_CHAIN_OPTION_TYPE(Enum): class STREAM_ACTIVES(Enum): pass + + +ENDPOINT_ARGUMENTS = { + 'search_instruments': { + 'projection': ['symbol-search', 'symbol-regex', 'desc-search', 'desc-regex', 'fundamental'] + }, + 'get_market_hours': { + 'markets': ['EQUITY', 'OPTION', 'FUTURE', 'BOND', 'FOREX'] + }, + 'get_movers': { + 'market': ['$DJI', '$COMPX', '$SPX.X'], + 'direction': ['up', 'down'], + 'change': ['value', 'percent'] + }, + 'get_user_principals': { + 'fields': ['streamerSubscriptionKeys', 'streamerConnectionInfo', 'preferences', 'surrogateIds'] + } +} + +VALID_CHART_VALUES = { + 'minute': { + 'day': [1, 2, 3, 4, 5, 10] + }, + 'daily': { + 'month': [1, 2, 3, 6], + 'year': [1, 2, 3, 5, 10, 15, 20], + 'ytd': [1] + }, + 'weekly': { + 'month': [1, 2, 3, 6], + 'year': [1, 2, 3, 5, 10, 15, 20], + 'ytd': [1] + }, + 'monthly': { + 'year': [1, 2, 3, 5, 10, 15, 20] + } +} + +STREAM_FIELD_IDS = { + "account_activity": { + "0": "subscription-key", + "1": "account-id", + "2": "message-type", + "3": "message-data" + }, + "level_one_forex": { + "0": "symbol", + "1": "bid-price", + "2": "ask-price", + "3": "last-price", + "4": "bid-size", + "5": "ask-size", + "6": "total-volume", + "7": "last-size", + "8": "quote-time", + "9": "trade-time", + "10": "high-price", + "11": "low-price", + "12": "close-price", + "13": "exchange-id", + "14": "description", + "15": "open-price", + "16": "net-change", + "17": "percent-change", + "18": "exchange-name", + "19": "digits", + "20": "security-status", + "21": "tick", + "22": "tick-amount", + "23": "product", + "24": "trading-hours", + "25": "is-tradable", + "26": "market-maker", + "27": "52-week-high", + "28": "52-week-low", + "29": "mark" + }, + "level_one_futures": { + "0": "symbol", + "1": "bid-price", + "2": "ask-price", + "3": "last-price", + "4": "bid-size", + "5": "ask-size", + "6": "ask-id", + "7": "bid-id", + "8": "total-volume", + "9": "last-size", + "10": "quote-time", + "11": "trade-time", + "12": "high-price", + "13": "low-price", + "14": "close-price", + "15": "exchange-id", + "16": "description", + "17": "last-id", + "18": "open-price", + "19": "net-change", + "20": "future-percent-change", + "21": "exhange-name", + "22": "security-status", + "23": "open-interest", + "24": "mark", + "25": "tick", + "26": "tick-amount", + "27": "product", + "28": "future-price-format", + "29": "future-trading-hours", + "30": "future-is-tradable", + "31": "future-multiplier", + "32": "future-is-active", + "33": "future-settlement-price", + "34": "future-active-symbol", + "35": "future-expiration-date" + }, + "level_one_futures_options": { + "0": "symbol", + "1": "bid-price", + "2": "ask-price", + "3": "last-price", + "4": "bid-size", + "5": "ask-size", + "6": "ask-id", + "7": "bid-id", + "8": "total-volume", + "9": "last-size", + "10": "quote-time", + "11": "trade-time", + "12": "high-price", + "13": "low-price", + "14": "close-price", + "15": "exchange-id", + "16": "description", + "17": "last-id", + "18": "open-price", + "19": "net-change", + "20": "future-percent-change", + "21": "exhange-name", + "22": "security-status", + "23": "open-interest", + "24": "mark", + "25": "tick", + "26": "tick-amount", + "27": "product", + "28": "future-price-format", + "29": "future-trading-hours", + "30": "future-is-tradable", + "31": "future-multiplier", + "32": "future-is-active", + "33": "future-settlement-price", + "34": "future-active-symbol", + "35": "future-expiration-date" + }, + "level_one_option": { + "0": "symbol", + "1": "description", + "2": "bid-price", + "3": "ask-price", + "4": "last-price", + "5": "high-price", + "6": "low-price", + "7": "close-price", + "8": "total-volume", + "9": "open-interest", + "10": "volatility", + "11": "quote-time", + "12": "trade-time", + "13": "money-intrinsic-value", + "14": "quote-day", + "15": "trade-day", + "16": "expiration-year", + "17": "multiplier", + "18": "digits", + "19": "open-price", + "20": "bid-size", + "21": "ask-size", + "22": "last-size", + "23": "net-change", + "24": "strike-price", + "25": "contract-type", + "26": "underlying", + "27": "expiration-month", + "28": "deliverables", + "29": "time-value", + "30": "expiration-day", + "31": "days-to-expiration", + "32": "delta", + "33": "gamma", + "34": "theta", + "35": "vega", + "36": "rho", + "37": "security-status", + "38": "theoretical-option-value", + "39": "underlying-price", + "40": "uv-expiration-type", + "41": "mark" + }, + "level_one_quote": { + "0": "symbol", + "1": "bid-price", + "2": "ask-price", + "3": "last-price", + "4": "bid-size", + "5": "ask-size", + "6": "ask-id", + "7": "bid-id", + "8": "total-volume", + "9": "last-size", + "10": "trade-time", + "11": "quote-time", + "12": "high-price", + "13": "low-price", + "14": "bid-tick", + "15": "close-price", + "16": "exchange-id", + "17": "marginable", + "18": "shortable", + "19": "island-bid", + "20": "island-ask", + "21": "island-volume", + "22": "quote-day", + "23": "trade-day", + "24": "volatility", + "25": "description", + "26": "last-id", + "27": "digits", + "28": "open-price", + "29": "net-change", + "30": "52-week-high", + "31": "52-week-low", + "32": "pe-ratio", + "33": "dividend-amount", + "34": "dividend-yield", + "35": "island-bid-size", + "36": "island-ask-size", + "37": "nav", + "38": "fund-price", + "39": "exchange-name", + "40": "dividend-date", + "41": "regular-market-quote", + "42": "regular-market-trade", + "43": "regular-market-last-price", + "44": "regular-market-last-size", + "45": "regular-market-trade-time", + "46": "regular-market-trade-day", + "47": "regular-market-net-change", + "48": "security-status", + "49": "mark", + "50": "quote-time-in-long", + "51": "trade-time-in-long", + "52": "regular-market-trade-time-in-long" + }, + "news_headline": { + "0": "symbol", + "1": "error-code", + "2": "story-datetime", + "3": "headline-id", + "4": "status", + "5": "headline", + "6": "story-id", + "7": "count-for-keyword", + "8": "keyword-array", + "9": "is-hot", + "10": "story-source" + }, + "qos_request": { + "0": "express", + "1": "real-time", + "2": "fast", + "3": "moderate", + "4": "slow", + "5": "delayed" + }, + "timesale": { + "0": "symbol", + "1": "trade-time", + "2": "last-price", + "3": "last-size", + "4": "last-sequence" + }, + "chart_equity": { + "seq": "chart-sequence", + "key": "symbol", + "1": "open-price", + "2": "high-price", + "3": "low-price", + "4": "close_price", + "5": "volume", + "6": "sequence", + "7": "chart_time", + "8": "chart_day" + }, + "chart_options": { + "seq": "chart-sequence", + "key": "key", + "1": "open-price", + "2": "high-price", + "3": "low-price", + "4": "close_price", + "5": "volume", + "6": "sequence", + "7": "chart_time", + "8": "chart_day" + }, + "chart_futures": { + "seq": "chart-sequence", + "key": "key", + "1": "open-price", + "2": "high-price", + "3": "low-price", + "4": "close_price", + "5": "volume", + "6": "sequence", + "7": "chart_time", + "8": "chart_day" + }, + "level_two_quotes": { + "0": "key", + "1": "time", + "2": "data" + }, + "level_two_nyse": { + "0": "key", + "1": "time", + "2": "data" + }, + "level_two_options": { + "0": "key", + "1": "time", + "2": "data" + }, + "level_two_forex": { + "0": "key", + "1": "time", + "2": "data" + }, + "level_two_nasdaq": { + "0": "key", + "1": "time", + "2": "data" + }, + "level_two_futures": { + "0": "key", + "1": "time", + "2": "data" + } +} + + +CSV_FIELD_KEYS = { + "ACTIVES_NASDAQ": { + "key": "key", + "1": "data" + }, + "ACTIVES_OTCBB": { + "key": "key", + "1": "data" + }, + "ACTIVES_NYSE": { + "key": "key", + "1": "data" + }, + "ACTIVES_OPTIONS": { + "key": "key", + "1": "data" + }, + "CHART_EQUITY": { + "seq": "chart-sequence", + "key": "symbol", + "1": "chart-time", + "2": "open-price", + "3": "high-price", + "4": "low-price", + "5": "close-price", + "6": "volume", + "7": "chart-time", + "8": "chart-day" + }, + "CHART_FUTURES": { + "seq": "chart-sequence", + "key": "symbol", + "1": "chart-time", + "2": "open-price", + "3": "high-price", + "4": "low-price", + "5": "close-price", + "6": "volume" + }, + "CHART_OPTIONS": { + "seq": "chart-sequence", + "key": "symbol", + "1": "chart-time", + "2": "open-price", + "3": "high-price", + "4": "low-price", + "5": "close-price", + "6": "volume" + }, + "CHART_HISTORY": { + "seq": "chart-sequence", + "key": "symbol", + "1": "chart-time", + "2": "open-price", + "3": "high-price", + "4": "low-price", + "5": "close-price", + "6": "volume", + "7": "chart-time", + "8": "chart-day" + }, + "CHART_HISTORY_FUTURES": { + "seq": "chart-sequence", + "key": "symbol", + "0": "key", + "1": "chart-time", + "2": "open-price", + "3": "high-price", + "4": "low-price", + "5": "close-price", + "6": "volume", + "7": "chart-time", + "8": "chart-day" + }, + "LEVELONE_FOREX": { + "1": "bid-price", + "10": "high-price", + "11": "low-price", + "12": "close-price", + "13": "exchange-id", + "14": "description", + "15": "open-price", + "16": "net-change", + "17": "percent-change", + "18": "exchange-name", + "19": "digits", + "2": "ask-price", + "20": "security-status", + "21": "tick", + "22": "tick-amount", + "23": "product", + "24": "trading-hours", + "25": "is-tradable", + "26": "market-maker", + "27": "52-week-high", + "28": "52-week-low", + "29": "mark", + "3": "last-price", + "4": "bid-size", + "5": "ask-size", + "6": "total-volume", + "7": "last-size", + "8": "quote-time", + "9": "trade-time", + "assetMainType": "asset-main-type", + "assetSubType": "asset-sub-type", + "cusip": "cusip", + "delayed": "delayed", + "key": "symbol", + }, + "LEVELONE_FUTURES": { + "1": "bid-price", + "10": "quote-time", + "11": "trade-time", + "12": "high-price", + "13": "low-price", + "14": "close-price", + "15": "exchange-id", + "16": "description", + "17": "last-id", + "18": "open-price", + "19": "net-change", + "2": "ask-price", + "20": "future-percent-change", + "21": "exhange-name", + "22": "security-status", + "23": "open-interest", + "24": "mark", + "25": "tick", + "26": "tick-amount", + "27": "product", + "28": "future-price-format", + "29": "future-trading-hours", + "3": "last-price", + "30": "future-is-tradable", + "31": "future-multiplier", + "32": "future-is-active", + "33": "future-settlement-price", + "34": "future-active-symbol", + "35": "future-expiration-date", + "4": "bid-size", + "5": "ask-size", + "6": "ask-id", + "7": "bid-id", + "8": "total-volume", + "9": "last-size", + "assetMainType": "asset-main-type", + "assetSubType": "asset-sub-type", + "cusip": "cusip", + "delayed": "delayed", + "key": "symbol", + }, + "LEVELONE_FUTURES_OPTIONS": { + "1": "bid-price", + "10": "quote-time", + "11": "trade-time", + "12": "high-price", + "13": "low-price", + "14": "close-price", + "15": "exchange-id", + "16": "description", + "17": "last-id", + "18": "open-price", + "19": "net-change", + "2": "ask-price", + "20": "future-percent-change", + "21": "exhange-name", + "22": "security-status", + "23": "open-interest", + "24": "mark", + "25": "tick", + "26": "tick-amount", + "27": "product", + "28": "future-price-format", + "29": "future-trading-hours", + "3": "last-price", + "30": "future-is-tradable", + "31": "future-multiplier", + "32": "future-is-active", + "33": "future-settlement-price", + "34": "future-active-symbol", + "35": "future-expiration-date", + "4": "bid-size", + "5": "ask-size", + "6": "ask-id", + "7": "bid-id", + "8": "total-volume", + "9": "last-size", + "assetMainType": "asset-main-type", + "assetSubType": "asset-sub-type", + "cusip": "cusip", + "delayed": "delayed", + "key": "symbol", + }, + "OPTION": { + "1": "description", + "10": "volatility", + "11": "quote-time", + "12": "trade-time", + "13": "money-intrinsic-value", + "14": "quote-day", + "15": "trade-day", + "16": "expiration-year", + "17": "multiplier", + "18": "digits", + "19": "open-price", + "2": "bid-price", + "20": "bid-size", + "21": "ask-size", + "22": "last-size", + "23": "net-change", + "24": "strike-price", + "25": "contract-type", + "26": "underlying", + "27": "expiration-month", + "28": "deliverables", + "29": "time-value", + "3": "ask-price", + "30": "expiration-day", + "31": "days-to-expiration", + "32": "delta", + "33": "gamma", + "34": "theta", + "35": "vega", + "36": "rho", + "37": "security-status", + "38": "theoretical-option-value", + "39": "underlying-price", + "4": "last-price", + "40": "uv-expiration-type", + "41": "mark", + "5": "high-price", + "6": "low-price", + "7": "close-price", + "8": "total-volume", + "9": "open-interest", + "assetMainType": "asset-main-type", + "assetSubType": "asset-sub-type", + "cusip": "cusip", + "delayed": "delayed", + "key": "symbol", + }, + "QUOTE": { + "10": "trade-time", + "11": "quote-time", + "12": "high-price", + "13": "low-price", + "14": "bid-tick", + "15": "close-price", + "16": "exchange-id", + "17": "marginable", + "18": "shortable", + "1": "bid-price", + "19": "island-bid", + "20": "island-ask", + "21": "island-volume", + "22": "quote-day", + "23": "trade-day", + "24": "volatility", + "25": "description", + "26": "last-id", + "27": "digits", + "28": "open-price", + "2": "ask-price", + "29": "net-change", + "30": "52-week-high", + "31": "52-week-low", + "32": "pe-ratio", + "33": "dividend-amount", + "34": "dividend-yield", + "35": "island-bid-size", + "36": "island-ask-size", + "37": "nav", + "38": "fund-price", + "3": "last-price", + "39": "exchange-name", + "40": "dividend-date", + "41": "regular-market-quote", + "42": "regular-market-trade", + "43": "regular-market-last-price", + "44": "regular-market-last-size", + "45": "regular-market-trade-time", + "46": "regular-market-trade-day", + "47": "regular-market-net-change", + "48": "security-status", + "4": "bid-size", + "49": "mark", + "50": "quote-time-in-long", + "51": "trade-time-in-long", + "5": "ask-size", + "6": "ask-id", + "7": "bid-id", + "8": "total-volume", + "9": "last-size", + "assetMainType": "asset-main-type", + "assetSubType": "asset-sub-type", + "cusip": "cusip", + "delayed": "delayed", + "key": "symbol" + }, + "NEWS_HEADLINE": { + "1": "error-code", + "10": "story-source", + "2": "story-datetime", + "3": "headline-id", + "4": "status", + "5": "headline", + "6": "story-id", + "7": "count-for-keyword", + "8": "keyword-array", + "9": "is-hot", + "key": "symbol", + "seq": "sequence" + }, + "TIMESALE_EQUITY": { + "1": "trade-time", + "2": "last-price", + "3": "last-size", + "4": "last-sequence", + "key": "symbol", + "seq": "sequence" + }, + "TIMESALE_FUTURES": { + "1": "trade-time", + "2": "last-price", + "3": "last-size", + "4": "last-sequence", + "key": "symbol", + "seq": "sequence" + }, + "TIMESALE_FOREX": { + "1": "trade-time", + "2": "last-price", + "3": "last-size", + "4": "last-sequence", + "key": "symbol", + "seq": "sequence" + }, + "TIMESALE_OPTIONS": { + "1": "trade-time", + "2": "last-price", + "3": "last-size", + "4": "last-sequence", + "key": "symbol", + "seq": "sequence" + }, +} + +CSV_FIELD_KEYS_LEVEL_2 = { + "NASDAQ_BOOK": "nested", + "OPTIONS_BOOK": "nested", + "LISTED_BOOK": "nested", + "FUTURES_BOOK": "nested" +} diff --git a/td/exceptions.py b/td/exceptions.py index bd536bc..3779fdb 100644 --- a/td/exceptions.py +++ b/td/exceptions.py @@ -1,14 +1,16 @@ class TknExpError(Exception): """Raise exception when refresh or access token is expired. - Args: - Exception (Exception): The base python exception class + Arguments: + ---- + Exception (Exception): The base python exception class """ def __init__(self, message): """Print out message for this exception. - Args: - message (str): Pass in the message returned by the server. + Arguments: + ---- + message (str): Pass in the message returned by the server. """ self.message = message super().__init__(self.message) @@ -16,14 +18,16 @@ def __init__(self, message): class ExdLmtError(Exception): """Raise exception when exceeding query limit of the server. - Args: - Exception (Exception): The base python exception class + Arguments: + ---- + Exception (Exception): The base python exception class """ def __init__(self, message): """Print out message for this exception. - Args: - message (str): Pass in the message returned by the server. + Arguments: + ---- + message (str): Pass in the message returned by the server. """ self.message = message super().__init__(self.message) @@ -31,14 +35,16 @@ def __init__(self, message): class NotNulError(Exception): """Raise exception when a null value is passed into non-null field. - Args: - Exception (Exception): The base python exception class + Arguments: + ---- + Exception (Exception): The base python exception class """ def __init__(self, message): """Print out message for this exception. - Args: - message (str): Pass in the message returned by the server. + Arguments: + ---- + message (str): Pass in the message returned by the server. """ self.message = message super().__init__(self.message) @@ -47,14 +53,16 @@ class ForbidError(Exception): """Raise forbidden exception. This usually occurs when the app does not have access to the account. - Args: - Exception (Exception): The base python exception class + Arguments: + ---- + Exception (Exception): The base python exception class """ def __init__(self, message): """Print out message for this exception. - Args: - message (str): Pass in the message returned by the server. + Arguments: + ---- + message (str): Pass in the message returned by the server. """ self.message = message super().__init__(self.message) @@ -62,14 +70,16 @@ def __init__(self, message): class NotFndError(Exception): """Raise exception when criteria is not found. - Args: - Exception (Exception): The base python exception class + Arguments: + ---- + Exception (Exception): The base python exception class """ def __init__(self, message): """Print out message for this exception. - Args: - message (str): Pass in the message returned by the server. + Arguments: + ---- + message (str): Pass in the message returned by the server. """ self.message = message super().__init__(self.message) @@ -78,14 +88,16 @@ class ServerError(Exception): """Raise exception when there is an error with the service or the server cannot provide response. - Args: - Exception (Exception): The base python exception class + Arguments: + ---- + Exception (Exception): The base python exception class """ def __init__(self, message): """Print out message for this exception. - Args: - message (str): Pass in the message returned by the server. + Arguments: + ---- + message (str): Pass in the message returned by the server. """ self.message = message super().__init__(self.message) @@ -94,14 +106,16 @@ class GeneralError(Exception): """Raise exception for all other status code >400 errors which are not defined above. - Args: - Exception (Exception): The base python exception class + Arguments: + ---- + Exception (Exception): The base python exception class """ def __init__(self, message): """Print out message for this exception. - Args: - message (str): Pass in the message returned by the server. + Arguments: + ---- + message (str): Pass in the message returned by the server. """ self.message = message super().__init__(self.message) \ No newline at end of file diff --git a/td/fields.py b/td/fields.py deleted file mode 100644 index bc3ae53..0000000 --- a/td/fields.py +++ /dev/null @@ -1,701 +0,0 @@ -ENDPOINT_ARGUMENTS = { - 'search_instruments': { - 'projection': ['symbol-search', 'symbol-regex', 'desc-search', 'desc-regex', 'fundamental'] - }, - 'get_market_hours': { - 'markets': ['EQUITY', 'OPTION', 'FUTURE', 'BOND', 'FOREX'] - }, - 'get_movers': { - 'market': ['$DJI', '$COMPX', '$SPX.X'], - 'direction': ['up', 'down'], - 'change': ['value', 'percent'] - }, - 'get_user_principals': { - 'fields': ['streamerSubscriptionKeys', 'streamerConnectionInfo', 'preferences', 'surrogateIds'] - } -} - -VALID_CHART_VALUES = { - 'minute': { - 'day': [1, 2, 3, 4, 5, 10] - }, - 'daily': { - 'month': [1, 2, 3, 6], - 'year': [1, 2, 3, 5, 10, 15, 20], - 'ytd': [1] - }, - 'weekly': { - 'month': [1, 2, 3, 6], - 'year': [1, 2, 3, 5, 10, 15, 20], - 'ytd': [1] - }, - 'monthly': { - 'year': [1, 2, 3, 5, 10, 15, 20] - } -} - -STREAM_FIELD_IDS = { - "account_activity": { - "0": "subscription-key", - "1": "account-id", - "2": "message-type", - "3": "message-data" - }, - "level_one_forex": { - "0": "symbol", - "1": "bid-price", - "2": "ask-price", - "3": "last-price", - "4": "bid-size", - "5": "ask-size", - "6": "total-volume", - "7": "last-size", - "8": "quote-time", - "9": "trade-time", - "10": "high-price", - "11": "low-price", - "12": "close-price", - "13": "exchange-id", - "14": "description", - "15": "open-price", - "16": "net-change", - "17": "percent-change", - "18": "exchange-name", - "19": "digits", - "20": "security-status", - "21": "tick", - "22": "tick-amount", - "23": "product", - "24": "trading-hours", - "25": "is-tradable", - "26": "market-maker", - "27": "52-week-high", - "28": "52-week-low", - "29": "mark" - }, - "level_one_futures": { - "0": "symbol", - "1": "bid-price", - "2": "ask-price", - "3": "last-price", - "4": "bid-size", - "5": "ask-size", - "6": "ask-id", - "7": "bid-id", - "8": "total-volume", - "9": "last-size", - "10": "quote-time", - "11": "trade-time", - "12": "high-price", - "13": "low-price", - "14": "close-price", - "15": "exchange-id", - "16": "description", - "17": "last-id", - "18": "open-price", - "19": "net-change", - "20": "future-percent-change", - "21": "exhange-name", - "22": "security-status", - "23": "open-interest", - "24": "mark", - "25": "tick", - "26": "tick-amount", - "27": "product", - "28": "future-price-format", - "29": "future-trading-hours", - "30": "future-is-tradable", - "31": "future-multiplier", - "32": "future-is-active", - "33": "future-settlement-price", - "34": "future-active-symbol", - "35": "future-expiration-date" - }, - "level_one_futures_options": { - "0": "symbol", - "1": "bid-price", - "2": "ask-price", - "3": "last-price", - "4": "bid-size", - "5": "ask-size", - "6": "ask-id", - "7": "bid-id", - "8": "total-volume", - "9": "last-size", - "10": "quote-time", - "11": "trade-time", - "12": "high-price", - "13": "low-price", - "14": "close-price", - "15": "exchange-id", - "16": "description", - "17": "last-id", - "18": "open-price", - "19": "net-change", - "20": "future-percent-change", - "21": "exhange-name", - "22": "security-status", - "23": "open-interest", - "24": "mark", - "25": "tick", - "26": "tick-amount", - "27": "product", - "28": "future-price-format", - "29": "future-trading-hours", - "30": "future-is-tradable", - "31": "future-multiplier", - "32": "future-is-active", - "33": "future-settlement-price", - "34": "future-active-symbol", - "35": "future-expiration-date" - }, - "level_one_option": { - "0": "symbol", - "1": "description", - "2": "bid-price", - "3": "ask-price", - "4": "last-price", - "5": "high-price", - "6": "low-price", - "7": "close-price", - "8": "total-volume", - "9": "open-interest", - "10": "volatility", - "11": "quote-time", - "12": "trade-time", - "13": "money-intrinsic-value", - "14": "quote-day", - "15": "trade-day", - "16": "expiration-year", - "17": "multiplier", - "18": "digits", - "19": "open-price", - "20": "bid-size", - "21": "ask-size", - "22": "last-size", - "23": "net-change", - "24": "strike-price", - "25": "contract-type", - "26": "underlying", - "27": "expiration-month", - "28": "deliverables", - "29": "time-value", - "30": "expiration-day", - "31": "days-to-expiration", - "32": "delta", - "33": "gamma", - "34": "theta", - "35": "vega", - "36": "rho", - "37": "security-status", - "38": "theoretical-option-value", - "39": "underlying-price", - "40": "uv-expiration-type", - "41": "mark" - }, - "level_one_quote": { - "0": "symbol", - "1": "bid-price", - "2": "ask-price", - "3": "last-price", - "4": "bid-size", - "5": "ask-size", - "6": "ask-id", - "7": "bid-id", - "8": "total-volume", - "9": "last-size", - "10": "trade-time", - "11": "quote-time", - "12": "high-price", - "13": "low-price", - "14": "bid-tick", - "15": "close-price", - "16": "exchange-id", - "17": "marginable", - "18": "shortable", - "19": "island-bid", - "20": "island-ask", - "21": "island-volume", - "22": "quote-day", - "23": "trade-day", - "24": "volatility", - "25": "description", - "26": "last-id", - "27": "digits", - "28": "open-price", - "29": "net-change", - "30": "52-week-high", - "31": "52-week-low", - "32": "pe-ratio", - "33": "dividend-amount", - "34": "dividend-yield", - "35": "island-bid-size", - "36": "island-ask-size", - "37": "nav", - "38": "fund-price", - "39": "exchange-name", - "40": "dividend-date", - "41": "regular-market-quote", - "42": "regular-market-trade", - "43": "regular-market-last-price", - "44": "regular-market-last-size", - "45": "regular-market-trade-time", - "46": "regular-market-trade-day", - "47": "regular-market-net-change", - "48": "security-status", - "49": "mark", - "50": "quote-time-in-long", - "51": "trade-time-in-long", - "52": "regular-market-trade-time-in-long" - }, - "news_headline": { - "0": "symbol", - "1": "error-code", - "2": "story-datetime", - "3": "headline-id", - "4": "status", - "5": "headline", - "6": "story-id", - "7": "count-for-keyword", - "8": "keyword-array", - "9": "is-hot", - "10": "story-source" - }, - "qos_request": { - "0": "express", - "1": "real-time", - "2": "fast", - "3": "moderate", - "4": "slow", - "5": "delayed" - }, - "timesale": { - "0": "symbol", - "1": "trade-time", - "2": "last-price", - "3": "last-size", - "4": "last-sequence" - }, - "chart_equity": { - "seq": "chart-sequence", - "key": "symbol", - "1": "open-price", - "2": "high-price", - "3": "low-price", - "4": "close_price", - "5": "volume", - "6": "sequence", - "7": "chart_time", - "8": "chart_day" - }, - "chart_options": { - "seq": "chart-sequence", - "key": "key", - "1": "open-price", - "2": "high-price", - "3": "low-price", - "4": "close_price", - "5": "volume", - "6": "sequence", - "7": "chart_time", - "8": "chart_day" - }, - "chart_futures": { - "seq": "chart-sequence", - "key": "key", - "1": "open-price", - "2": "high-price", - "3": "low-price", - "4": "close_price", - "5": "volume", - "6": "sequence", - "7": "chart_time", - "8": "chart_day" - }, - "level_two_quotes": { - "0": "key", - "1": "time", - "2": "data" - }, - "level_two_nyse": { - "0": "key", - "1": "time", - "2": "data" - }, - "level_two_options": { - "0": "key", - "1": "time", - "2": "data" - }, - "level_two_forex": { - "0": "key", - "1": "time", - "2": "data" - }, - "level_two_nasdaq": { - "0": "key", - "1": "time", - "2": "data" - }, - "level_two_futures": { - "0": "key", - "1": "time", - "2": "data" - } -} - - -CSV_FIELD_KEYS = { - "ACTIVES_NASDAQ": { - "key": "key", - "1": "data" - }, - "ACTIVES_OTCBB": { - "key": "key", - "1": "data" - }, - "ACTIVES_NYSE": { - "key": "key", - "1": "data" - }, - "ACTIVES_OPTIONS": { - "key": "key", - "1": "data" - }, - "CHART_EQUITY": { - "seq": "chart-sequence", - "key": "symbol", - "1": "chart-time", - "2": "open-price", - "3": "high-price", - "4": "low-price", - "5": "close-price", - "6": "volume", - "7": "chart-time", - "8": "chart-day" - }, - "CHART_FUTURES": { - "seq": "chart-sequence", - "key": "symbol", - "1": "chart-time", - "2": "open-price", - "3": "high-price", - "4": "low-price", - "5": "close-price", - "6": "volume" - }, - "CHART_OPTIONS": { - "seq": "chart-sequence", - "key": "symbol", - "1": "chart-time", - "2": "open-price", - "3": "high-price", - "4": "low-price", - "5": "close-price", - "6": "volume" - }, - "CHART_HISTORY": { - "seq": "chart-sequence", - "key": "symbol", - "1": "chart-time", - "2": "open-price", - "3": "high-price", - "4": "low-price", - "5": "close-price", - "6": "volume", - "7": "chart-time", - "8": "chart-day" - }, - "CHART_HISTORY_FUTURES": { - "seq": "chart-sequence", - "key": "symbol", - "0": "key", - "1": "chart-time", - "2": "open-price", - "3": "high-price", - "4": "low-price", - "5": "close-price", - "6": "volume", - "7": "chart-time", - "8": "chart-day" - }, - "LEVELONE_FOREX": { - "1": "bid-price", - "10": "high-price", - "11": "low-price", - "12": "close-price", - "13": "exchange-id", - "14": "description", - "15": "open-price", - "16": "net-change", - "17": "percent-change", - "18": "exchange-name", - "19": "digits", - "2": "ask-price", - "20": "security-status", - "21": "tick", - "22": "tick-amount", - "23": "product", - "24": "trading-hours", - "25": "is-tradable", - "26": "market-maker", - "27": "52-week-high", - "28": "52-week-low", - "29": "mark", - "3": "last-price", - "4": "bid-size", - "5": "ask-size", - "6": "total-volume", - "7": "last-size", - "8": "quote-time", - "9": "trade-time", - "assetMainType": "asset-main-type", - "assetSubType": "asset-sub-type", - "cusip": "cusip", - "delayed": "delayed", - "key": "symbol", - }, - "LEVELONE_FUTURES": { - "1": "bid-price", - "10": "quote-time", - "11": "trade-time", - "12": "high-price", - "13": "low-price", - "14": "close-price", - "15": "exchange-id", - "16": "description", - "17": "last-id", - "18": "open-price", - "19": "net-change", - "2": "ask-price", - "20": "future-percent-change", - "21": "exhange-name", - "22": "security-status", - "23": "open-interest", - "24": "mark", - "25": "tick", - "26": "tick-amount", - "27": "product", - "28": "future-price-format", - "29": "future-trading-hours", - "3": "last-price", - "30": "future-is-tradable", - "31": "future-multiplier", - "32": "future-is-active", - "33": "future-settlement-price", - "34": "future-active-symbol", - "35": "future-expiration-date", - "4": "bid-size", - "5": "ask-size", - "6": "ask-id", - "7": "bid-id", - "8": "total-volume", - "9": "last-size", - "assetMainType": "asset-main-type", - "assetSubType": "asset-sub-type", - "cusip": "cusip", - "delayed": "delayed", - "key": "symbol", - }, - "LEVELONE_FUTURES_OPTIONS": { - "1": "bid-price", - "10": "quote-time", - "11": "trade-time", - "12": "high-price", - "13": "low-price", - "14": "close-price", - "15": "exchange-id", - "16": "description", - "17": "last-id", - "18": "open-price", - "19": "net-change", - "2": "ask-price", - "20": "future-percent-change", - "21": "exhange-name", - "22": "security-status", - "23": "open-interest", - "24": "mark", - "25": "tick", - "26": "tick-amount", - "27": "product", - "28": "future-price-format", - "29": "future-trading-hours", - "3": "last-price", - "30": "future-is-tradable", - "31": "future-multiplier", - "32": "future-is-active", - "33": "future-settlement-price", - "34": "future-active-symbol", - "35": "future-expiration-date", - "4": "bid-size", - "5": "ask-size", - "6": "ask-id", - "7": "bid-id", - "8": "total-volume", - "9": "last-size", - "assetMainType": "asset-main-type", - "assetSubType": "asset-sub-type", - "cusip": "cusip", - "delayed": "delayed", - "key": "symbol", - }, - "OPTION": { - "1": "description", - "10": "volatility", - "11": "quote-time", - "12": "trade-time", - "13": "money-intrinsic-value", - "14": "quote-day", - "15": "trade-day", - "16": "expiration-year", - "17": "multiplier", - "18": "digits", - "19": "open-price", - "2": "bid-price", - "20": "bid-size", - "21": "ask-size", - "22": "last-size", - "23": "net-change", - "24": "strike-price", - "25": "contract-type", - "26": "underlying", - "27": "expiration-month", - "28": "deliverables", - "29": "time-value", - "3": "ask-price", - "30": "expiration-day", - "31": "days-to-expiration", - "32": "delta", - "33": "gamma", - "34": "theta", - "35": "vega", - "36": "rho", - "37": "security-status", - "38": "theoretical-option-value", - "39": "underlying-price", - "4": "last-price", - "40": "uv-expiration-type", - "41": "mark", - "5": "high-price", - "6": "low-price", - "7": "close-price", - "8": "total-volume", - "9": "open-interest", - "assetMainType": "asset-main-type", - "assetSubType": "asset-sub-type", - "cusip": "cusip", - "delayed": "delayed", - "key": "symbol", - }, - "QUOTE": { - "10": "trade-time", - "11": "quote-time", - "12": "high-price", - "13": "low-price", - "14": "bid-tick", - "15": "close-price", - "16": "exchange-id", - "17": "marginable", - "18": "shortable", - "1": "bid-price", - "19": "island-bid", - "20": "island-ask", - "21": "island-volume", - "22": "quote-day", - "23": "trade-day", - "24": "volatility", - "25": "description", - "26": "last-id", - "27": "digits", - "28": "open-price", - "2": "ask-price", - "29": "net-change", - "30": "52-week-high", - "31": "52-week-low", - "32": "pe-ratio", - "33": "dividend-amount", - "34": "dividend-yield", - "35": "island-bid-size", - "36": "island-ask-size", - "37": "nav", - "38": "fund-price", - "3": "last-price", - "39": "exchange-name", - "40": "dividend-date", - "41": "regular-market-quote", - "42": "regular-market-trade", - "43": "regular-market-last-price", - "44": "regular-market-last-size", - "45": "regular-market-trade-time", - "46": "regular-market-trade-day", - "47": "regular-market-net-change", - "48": "security-status", - "4": "bid-size", - "49": "mark", - "50": "quote-time-in-long", - "51": "trade-time-in-long", - "5": "ask-size", - "6": "ask-id", - "7": "bid-id", - "8": "total-volume", - "9": "last-size", - "assetMainType": "asset-main-type", - "assetSubType": "asset-sub-type", - "cusip": "cusip", - "delayed": "delayed", - "key": "symbol" - }, - "NEWS_HEADLINE": { - "1": "error-code", - "10": "story-source", - "2": "story-datetime", - "3": "headline-id", - "4": "status", - "5": "headline", - "6": "story-id", - "7": "count-for-keyword", - "8": "keyword-array", - "9": "is-hot", - "key": "symbol", - "seq": "sequence" - }, - "TIMESALE_EQUITY": { - "1": "trade-time", - "2": "last-price", - "3": "last-size", - "4": "last-sequence", - "key": "symbol", - "seq": "sequence" - }, - "TIMESALE_FUTURES": { - "1": "trade-time", - "2": "last-price", - "3": "last-size", - "4": "last-sequence", - "key": "symbol", - "seq": "sequence" - }, - "TIMESALE_FOREX": { - "1": "trade-time", - "2": "last-price", - "3": "last-size", - "4": "last-sequence", - "key": "symbol", - "seq": "sequence" - }, - "TIMESALE_OPTIONS": { - "1": "trade-time", - "2": "last-price", - "3": "last-size", - "4": "last-sequence", - "key": "symbol", - "seq": "sequence" - }, -} - -CSV_FIELD_KEYS_LEVEL_2 = { - "NASDAQ_BOOK": "nested", - "OPTIONS_BOOK": "nested", - "LISTED_BOOK": "nested", - "FUTURES_BOOK": "nested" -} diff --git a/td/stream.py b/td/stream.py index 89ae1bf..87fe1fa 100644 --- a/td/stream.py +++ b/td/stream.py @@ -12,9 +12,9 @@ import websockets -from td.fields import CSV_FIELD_KEYS -from td.fields import CSV_FIELD_KEYS_LEVEL_2 -from td.fields import STREAM_FIELD_IDS +from td.enums import CSV_FIELD_KEYS +from td.enums import CSV_FIELD_KEYS_LEVEL_2 +from td.enums import STREAM_FIELD_IDS class TDStreamerClient(): diff --git a/td/utils.py b/td/utils.py index 172157b..35b6934 100644 --- a/td/utils.py +++ b/td/utils.py @@ -1,33 +1,322 @@ import datetime +import os +import sys +import json +import pathlib -def milliseconds_since_epoch(dt_object: datetime.datetime) -> int: - """converts a datetime object to milliseconds since 1970, as an integer - - Arguments: - ---------- +from typing import Any +from typing import Dict +from typing import List +from typing import Union +from typing import Optional + + +class StatePath(type(pathlib.Path())): + + def __init__(self, credentials_file: str = None): + """Initalizes the `StatePath` Class object.""" + + self.credenitals_file_name = 'td_state.json' + self.settings_location = {} + + if credentials_file and isinstance(credentials_file, str): + self.credentials_file: pathlib.Path = pathlib.Path( + credentials_file + ) + else: + self.credentials_file: pathlib.Path = self.library_directory + + @property + def get_file_path(self) -> str: + """Resolves the file path. + + Returns: + ---- + (str): A file path reprsented as a string. + """ + + return self.credentials_file.absolute() + + def path_home(self) -> pathlib.PurePath: + """Determines the user's Home Path using Pathlib. + + Returns: + ---- + {pathlib.PurePath} -- A PurePath object that points to + the user's home directory. + """ + + home_directory = pathlib.Path.home() + return home_directory + + @property + def home_directory(self) -> pathlib.PurePath: + """Returns the Home directory path. + + Returns: + ---- + {pathlib.PurePath} -- A path object. + """ + return self.path_home() + + @property + def library_directory(self) -> pathlib.PurePath: + """Returns the TD Library directory path. + + Returns: + ---- + {pathlib.PurePath} -- A path object. + """ + return self.path_library() + + @property + def settings_directory(self) -> pathlib.PurePath: + """Returns the `.td_python_library` directory path. + + Returns: + ---- + {pathlib.PurePath} -- A path object. + """ + return self.path_settings() + + def path_library(self) -> pathlib.PurePath: + """Generates the TD Library Path. + + Returns: + ---- + {pathlib.PurePath} -- A PurePath object pointing to the TD + library. + """ + library_directory = pathlib.Path(__file__).parent + return library_directory + + def path_settings(self) -> pathlib.PurePath: + """Generates a path to the `.td_python_library` directory. + + Returns: + ---- + {pathlib.PurePath} -- A PurePath object pointing to the `.td_python_library` + directory. + """ + self.home_directory + settings_directory = self.home_directory.joinpath('.td_python_library') + return settings_directory + + def json_settings_path(self): + """Generates a path to the `.td_python_library/td_state.json` file. + + Returns: + ---- + {pathlib.PurePath} -- A PurePath object pointing to the + `.td_python_library/td_state.json` file. + """ + return self.settings_directory.joinpath(self.credenitals_file_name) + + def json_library_path(self): + """Generates a path to the `td/td_state.json` file. + + Returns: + ---- + {pathlib.PurePath} -- A PurePath object pointing to the + `td/td_state.json` file. + """ + return self.library_directory.joinpath(self.credenitals_file_name) + + @property + def does_credentials_file_exist(self): + """Sepcifies whether the passed through credentials file exists.""" + + return self.credentials_file.exists() + + def does_file_exist(self, file_path: pathlib.Path) -> bool: + """Checks if a file exists. + + Arguments: + ---- + file_path {pathlib.Path} -- A path to a specific file. + + Returns: + ---- + bool -- `True` if it exists, `False` if it does not exist. + """ + return file_path.exists() + + def does_directory_exist(self, file_path: pathlib.Path) -> bool: + """Checks if a directory exists. + + This takes a file path and checks if folder that the file is supposed + to exist in exists. It only does one level up. + + Arguments: + ---- + file_path {pathlib.Path} -- A path to a specific directory. + + Returns: + ---- + bool -- `True` if it exists, `False` if it does not exist. + """ + + if isinstance(file_path, str): + file_path = pathlib.Path(file_path).absolute() + directory = file_path + else: + file_path = file_path.absolute() + directory = file_path.parent + + # See if it exists + return directory.exists() + + def write_to_settings(self, state: dict) -> pathlib.Path: + """Writes the credentials to the Settigns folder. + + Arguments: + ---- + state {dict} -- The session state dictionary. + + Returns: + ---- + pathlib.Path -- The path to credentials path. + """ + + json_settings_path = self.json_settings_path() + + # Check to see if the folder exists. + if not self.does_directory_exist(file_path=json_settings_path): + json_settings_path.parent.mkdir() + + # write to the JSON file. + with open(file=json_settings_path, mode='w+') as credenitals_file: + json.dump(obj=state, fp=credenitals_file) + + return json_settings_path + + def write_credentials(self, file_path: Union[pathlib.Path, str], state: dict) -> pathlib.Path: + """Writes the credentials to the Settigns folder. + + Arguments: + ---- + file_path {Union[pathlib.Path, str]} -- The path to the credentials file. + + state {dict} -- The session state dictionary. + + Returns: + ---- + pathlib.Path -- The path to credentials path. + """ + + if isinstance(file_path, str): + json_path = pathlib.Path(file_path).absolute() + else: + json_path = file_path.absolute() + + # Check to see if the folder exists. + if not self.does_directory_exist(file_path=json_path): + json_path.parent.mkdir() + + # write to the JSON file. + with open(file=json_path, mode='w+') as credenitals_file: + json.dump(obj=state, fp=credenitals_file) + + return json_path + + def read_credentials(self, file_path: Union[pathlib.Path, str]) -> dict: + """Read the credentials file. + + Arguments: + ---- + file_path {Union[pathlib.Path, str]} -- The path to the credentials file. + + Returns: + ---- + {dict} -- The session state dictionary. + """ + + # Handle the file path input. + if isinstance(file_path, str): + json_path = pathlib.Path(file_path).absolute() + else: + json_path = file_path.absolute() + + # Check to see if the folder exists. + if not self.does_directory_exist(file_path=json_path): + raise FileNotFoundError("Credentials File does not exist.") + + # read the JSON file. + with open(file=json_path, mode='r') as credenitals_file: + state_dict = json.load(fp=credenitals_file) + + return state_dict + + def set_path(self, path: str) -> None: + """Sets the path to credentials file. + + Arguments: + ---- + path {str} -- The path to set. + """ + self._credentials_full_path = path + + def delete_credentials(self, file_path: pathlib.Path) -> None: + """Deletes the credential File. + + Arguments: + ---- + file_path (pathlib.Path): [description] + """ + + file_path.unlink() + + def define_settings_location(self, location_id: str, location: str) -> pathlib.Path: + """Used to set a custom settings location. + + Args: + ---- + location_id (str): The ID You want associated with this location. + + location (str): The file path to the settings file. + + Returns: + ---- + pathlib.Path: A Pathlib object representing the file location. + """ + + new_path = pathlib.Path(location) + + self.settings_location[location_id] = new_path + + return new_path + + +class TDUtilities(): + + def milliseconds_since_epoch(self, dt_object: datetime.datetime) -> int: + """converts a datetime object to milliseconds since 1970, as an integer + + Arguments: + ---------- dt_object {datetime.datetime} -- Python datetime object. - - Returns: - -------- + + Returns: + -------- [int] -- The timestamp in milliseconds since epoch. - """ + """ + + return int(dt_object.timestamp() * 1000) - return int(dt_object.timestamp() * 1000) + def datetime_from_milliseconds_since_epoch(self, ms_since_epoch: int, timezone: datetime.timezone = None) -> datetime.datetime: + """Converts milliseconds since epoch to a datetime object. -def datetime_from_milliseconds_since_epoch(ms_since_epoch: int, timezone: datetime.timezone = None) -> datetime.datetime: - """Converts milliseconds since epoch to a datetime object. - - Arguments: - ---------- + Arguments: + ---------- ms_since_epoch {int} -- Number of milliseconds since epoch. - - Keyword Arguments: - -------- + + Keyword Arguments: + -------- timezone {datetime.timezone} -- The timezone of the new datetime object. (default: {None}) - - Returns: - -------- + + Returns: + -------- datetime.datetime -- A python datetime object. - """ + """ - return datetime.datetime.fromtimestamp((ms_since_epoch / 1000), tz=timezone) + return datetime.datetime.fromtimestamp((ms_since_epoch / 1000), tz=timezone) diff --git a/tests/unit/fields.jsonc b/tests/unit/fields.jsonc index 423e562..aee95a2 100644 --- a/tests/unit/fields.jsonc +++ b/tests/unit/fields.jsonc @@ -1,172 +1,180 @@ { - "account_activity_fields": [ - "subscription-key", - "account-id", - "message-type", - "message-data" - ], - "level_one_forex_fields": [ - "symbol", - "bid-price", - "ask-price", - "last-price", - "bid-size", - "ask-size", - "total-volume", - "last-size", - "trade-time", - "quote-time", - "high-price", - "low-price", - "close-price", - "exchange-id", - "description", - "digits", - "open-price", - "net-change", - "52-week-low", - "exchange-name", - "security-status", - "mark", - "tick", - "tick-amount", - "product", - "percent-change", - "trading-hours", - "is-tradable", - "market-maker", - "52-week-high" - ], - "level_one_futures_options_fields": [ - "symbol", - "bid-price", - "ask-price", - "last-price", - "bid-size", - "ask-size", - "ask-id", - "bid-id", - "total-volume", - "last-size", - "trade-time", - "quote-time", - "high-price", - "low-price", - "close-price", - "exchange-id", - "description", - "last-id", - "open-price", - "net-change", - "security-status", - "mark", - "open-interest", - "future-percent-change", - "exhange-name", - "tick", - "tick-amount", - "product", - "future-price-format", - "future-trading-hours", - "future-is-tradable", - "future-multiplier", - "future-is-active", - "future-settlement-price", - "future-active-symbol", - "future-expiration-date" - ], - "level_one_quote_fields": [ - "symbol", - "bid-price", - "ask-price", - "last-price", - "bid-size", - "ask-size", - "ask-id", - "bid-id", - "total-volume", - "last-size", - "trade-time", - "quote-time", - "high-price", - "low-price", - "bid-tick", - "close-price", - "exchange-id", - "marginable", - "shortable", - "island-bid", - "island-ask", - "island-volume", - "quote-day", - "trade-day", - "volatility", - "description", - "last-id", - "digits", - "open-price", - "net-change", - "52 -week-high", - "52-week-low", - "pe-ratio", - "dividend-amount", - "dividend-yield", - "island-bid-size", - "island-ask-size", - "nav", - "fund-price", - "exchange-name", - "dividend-date", - "regular-market-quote", - "regular-market-trade", - "regular-market-last-price", - "regular-market-last-size", - "regular-market-trade-time", - "regular-market-trade-day", - "regular-market-net-change", - "security-status", - "mark", - "quote-time-in-long", - "trade-time-in-long", - "regular-market-trade-time-in-long" - ], - "news_headline_fields": [ - "symbol", - "error-code", - "story-datetime", - "headline-id", - "status", - "headline", - "story-id", - "count-for-keyword", - "keyword-array", - "is-hot", - "story-source" - ], - "qos_request_fields": [ - "express", - "real-time", - "fast", - "moderate", - "slow", - "delayed" - ], - "timesale_fields": [ - "symbol", - "last-price", - "last-size", - "trade-time", - "last-sequence" - ], - "chart_fields": [ - "key", - "open-price", - "high-price", - "low-price", - "close-price", - "volume", - "sequence", - "chart-time", - "chart-day" - ] -} \ No newline at end of file + // Define the Account Acitivity Fields. + "account_activity_fields": [ + "subscription-key", + "account-id", + "message-type", + "message-data" + ], + // Define the Level One Forex Fields. + "level_one_forex_fields": [ + "symbol", + "bid-price", + "ask-price", + "last-price", + "bid-size", + "ask-size", + "total-volume", + "last-size", + "trade-time", + "quote-time", + "high-price", + "low-price", + "close-price", + "exchange-id", + "description", + "digits", + "open-price", + "net-change", + "52-week-low", + "exchange-name", + "security-status", + "mark", + "tick", + "tick-amount", + "product", + "percent-change", + "trading-hours", + "is-tradable", + "market-maker", + "52-week-high" + ], + // Define the Level One Futures Options Fields. + "level_one_futures_options_fields": [ + "symbol", + "bid-price", + "ask-price", + "last-price", + "bid-size", + "ask-size", + "ask-id", + "bid-id", + "total-volume", + "last-size", + "trade-time", + "quote-time", + "high-price", + "low-price", + "close-price", + "exchange-id", + "description", + "last-id", + "open-price", + "net-change", + "security-status", + "mark", + "open-interest", + "future-percent-change", + "exhange-name", + "tick", + "tick-amount", + "product", + "future-price-format", + "future-trading-hours", + "future-is-tradable", + "future-multiplier", + "future-is-active", + "future-settlement-price", + "future-active-symbol", + "future-expiration-date" + ], + // Define the Level One Quotes Fields. + "level_one_quote_fields": [ + "symbol", + "bid-price", + "ask-price", + "last-price", + "bid-size", + "ask-size", + "ask-id", + "bid-id", + "total-volume", + "last-size", + "trade-time", + "quote-time", + "high-price", + "low-price", + "bid-tick", + "close-price", + "exchange-id", + "marginable", + "shortable", + "island-bid", + "island-ask", + "island-volume", + "quote-day", + "trade-day", + "volatility", + "description", + "last-id", + "digits", + "open-price", + "net-change", + "52 -week-high", + "52-week-low", + "pe-ratio", + "dividend-amount", + "dividend-yield", + "island-bid-size", + "island-ask-size", + "nav", + "fund-price", + "exchange-name", + "dividend-date", + "regular-market-quote", + "regular-market-trade", + "regular-market-last-price", + "regular-market-last-size", + "regular-market-trade-time", + "regular-market-trade-day", + "regular-market-net-change", + "security-status", + "mark", + "quote-time-in-long", + "trade-time-in-long", + "regular-market-trade-time-in-long" + ], + // Define the News Headlines Fields. + "news_headline_fields": [ + "symbol", + "error-code", + "story-datetime", + "headline-id", + "status", + "headline", + "story-id", + "count-for-keyword", + "keyword-array", + "is-hot", + "story-source" + ], + // Define the Quality of Service Fields. + "qos_request_fields": [ + "express", + "real-time", + "fast", + "moderate", + "slow", + "delayed" + ], + // Define the Timesale Fields. + "timesale_fields": [ + "symbol", + "last-price", + "last-size", + "trade-time", + "last-sequence" + ], + // Define the Chart Fields. + "chart_fields": [ + "key", + "open-price", + "high-price", + "low-price", + "close-price", + "volume", + "sequence", + "chart-time", + "chart-day" + ] +} diff --git a/tests/unit/test_order.py b/tests/unit/test_order.py index 75731da..5cc6699 100644 --- a/tests/unit/test_order.py +++ b/tests/unit/test_order.py @@ -1,10 +1,7 @@ import unittest - import td.enums as td_enums from unittest import TestCase -from datetime import datetime -from datetime import timedelta from configparser import ConfigParser from td.orders import Order @@ -12,6 +9,7 @@ from td.client import TDClient from td.stream import TDStreamerClient + class TDSession(TestCase): """Will perform a unit test for the TD session.""" @@ -30,10 +28,10 @@ def setUp(self) -> None: # Initalize the session. self.td_session = TDClient( - client_id=CLIENT_ID, - redirect_uri=REDIRECT_URI, + client_id=CLIENT_ID, + redirect_uri=REDIRECT_URI, credentials_path=JSON_PATH, - account_number = ACCOUNT_NUMBER + account_number=ACCOUNT_NUMBER ) self.td_order = Order() @@ -49,15 +47,41 @@ def test_creates_instance_of_session(self): def test_define_simple_order(self): """Test creating a simple order.""" - self.td_order.order_session(session=td_enums.ORDER_SESSION.NORMAL) - self.td_order.order_duration(duration=td_enums.DURATION.GOOD_TILL_CANCEL) - - self.td_order_leg.order_leg_instruction(instruction=td_enums.ORDER_INSTRUCTIONS.SELL) - self.td_order_leg.order_leg_price(price=112.50) - self.td_order_leg.order_leg_quantity(quantity=10) - self.td_order_leg.order_leg_asset(asset_type=td_enums.ORDER_ASSET_TYPE.EQUITY, symbol='MSFT') + # Add the Order session. + self.td_order.order_session( + session=td_enums.ORDER_SESSION.NORMAL + ) + + # Add the Order duration. + self.td_order.order_duration( + duration=td_enums.DURATION.GOOD_TILL_CANCEL + ) + + # Add the Order Leg Instruction. + self.td_order_leg.order_leg_instruction( + instruction=td_enums.ORDER_INSTRUCTIONS.SELL + ) - self.td_order.add_order_leg(order_leg=self.td_order_leg) + # Add the Order Leg price. + self.td_order_leg.order_leg_price( + price=112.50 + ) + + # Add the Order Leg quantity. + self.td_order_leg.order_leg_quantity( + quantity=10 + ) + + # Add the Order Leg Asset. + self.td_order_leg.order_leg_asset( + asset_type=td_enums.ORDER_ASSET_TYPE.EQUITY, + symbol='MSFT' + ) + + # Add the Order Leg. + self.td_order.add_order_leg( + order_leg=self.td_order_leg + ) correct_dict = { "session": "NORMAL", @@ -68,7 +92,7 @@ def test_define_simple_order(self): "price": 112.5, "quantity": 10, "instrument": { "assetType": - "EQUITY", + "EQUITY", "symbol": "MSFT" } } @@ -84,5 +108,6 @@ def tearDown(self): self.td_order = None self.td_order_leg = None + if __name__ == '__main__': unittest.main() diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py index 66edfc9..72037c9 100644 --- a/tests/unit/test_session.py +++ b/tests/unit/test_session.py @@ -74,7 +74,7 @@ def test_single_get_quotes(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_single_quotes.jsonc', 'w+') as data_file: + with open('samples/responses/sample_single_quotes.jsonc', 'w+') as data_file: json.dump(obj=quotes, fp=data_file, indent=3) def test_get_quotes(self): @@ -88,7 +88,7 @@ def test_get_quotes(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_multiple_quotes.jsonc', 'w+') as data_file: + with open('samples/responses/sample_multiple_quotes.jsonc', 'w+') as data_file: json.dump(obj=quotes, fp=data_file, indent=3) def test_get_accounts(self): @@ -105,7 +105,7 @@ def test_get_accounts(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_accounts.jsonc', 'w+') as data_file: + with open('samples/responses/sample_accounts.jsonc', 'w+') as data_file: json.dump(obj=accounts, fp=data_file, indent=3) def test_create_stream_session(self): @@ -129,7 +129,7 @@ def test_get_transactions(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_transaction_data.jsonc', 'w+') as data_file: + with open('samples/responses/sample_transaction_data.jsonc', 'w+') as data_file: json.dump(obj=transaction_data_multi, fp=data_file, indent=3) def test_get_market_hours(self): @@ -155,7 +155,7 @@ def test_get_market_hours(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_market_hours.jsonc', 'w+') as data_file: + with open('samples/responses/sample_market_hours.jsonc', 'w+') as data_file: json.dump(obj=market_hours_multi, fp=data_file, indent=3) def test_get_instrument(self): @@ -172,7 +172,7 @@ def test_get_instrument(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_instrument.jsonc', 'w+') as data_file: + with open('samples/responses/sample_instrument.jsonc', 'w+') as data_file: json.dump(obj=get_instrument, fp=data_file, indent=3) def test_chart_history(self): @@ -231,7 +231,7 @@ def test_chart_history(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_historical_prices.jsonc', 'w+') as data_file: + with open('samples/responses/sample_historical_prices.jsonc', 'w+') as data_file: json.dump(obj=historical_prices, fp=data_file, indent=3) def test_custom_historical_prices(self): @@ -275,7 +275,7 @@ def test_custom_historical_prices(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_historical_prices.jsonc', 'w+') as data_file: + with open('samples/responses/sample_historical_prices.jsonc', 'w+') as data_file: json.dump(obj=historical_custom, fp=data_file, indent=3) def test_search_instruments(self): @@ -292,7 +292,7 @@ def test_search_instruments(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_search_instrument.jsonc', 'w+') as data_file: + with open('samples/responses/sample_search_instrument.jsonc', 'w+') as data_file: json.dump(obj=instrument_search_data, fp=data_file, indent=3) def test_get_movers(self): @@ -314,7 +314,7 @@ def test_get_movers(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_movers.jsonc', 'w+') as data_file: + with open('samples/responses/sample_movers.jsonc', 'w+') as data_file: json.dump(obj=movers_data, fp=data_file, indent=3) def test_get_user_preferences(self): @@ -330,7 +330,7 @@ def test_get_user_preferences(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_account_preferences.jsonc', 'w+') as data_file: + with open('samples/responses/sample_account_preferences.jsonc', 'w+') as data_file: json.dump(obj=preference_data, fp=data_file, indent=3) def test_get_user_principals(self): @@ -345,7 +345,7 @@ def test_get_user_principals(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_user_principals.jsonc', 'w+') as data_file: + with open('samples/responses/sample_user_principals.jsonc', 'w+') as data_file: json.dump(obj=user_principals, fp=data_file, indent=3) def test_get_streamer_keys(self): @@ -361,7 +361,7 @@ def test_get_streamer_keys(self): # Save the data. if SAVE_FLAG: - with open(r'samples\responses\sample_streamer_keys.jsonc', 'w+') as data_file: + with open('samples/responses/sample_streamer_keys.jsonc', 'w+') as data_file: json.dump(obj=streamer_keys, fp=data_file, indent=3) def tearDown(self) -> None: diff --git a/tests/unit/test_statepath.py b/tests/unit/test_statepath.py index e18798e..b40f2de 100644 --- a/tests/unit/test_statepath.py +++ b/tests/unit/test_statepath.py @@ -118,7 +118,7 @@ def test_write_to_custom(self) -> None: """Test writing to a User Provided Path.""" # Define the file path. - file_path = r'config\td_state_custom.json' + file_path = 'config/td_state_custom.json' # Define the truth. truth = pathlib.Path(__file__).parents[2].joinpath(file_path) @@ -163,7 +163,7 @@ def test_read_from_custom(self) -> None: truth = {'value': 'custom'} - file_path = pathlib.Path(r'config\td_state_custom.json') + file_path = pathlib.Path('config/td_state_custom.json') check = self.state_path.read_credentials(file_path=file_path) @@ -174,7 +174,7 @@ def test_read_from_non_exist(self) -> None: """Test writing to a User Provided Path.""" truth = 'Credentials File does not exist.' - file_path = pathlib.Path(r'config\no\td_state_custom.json') + file_path = pathlib.Path('config/no/td_state_custom.json') with self.assertRaises(FileNotFoundError) as context: self.state_path.read_credentials(file_path=file_path) diff --git a/tests/unit/test_stream.py b/tests/unit/test_stream.py index 4a3594b..905dac6 100644 --- a/tests/unit/test_stream.py +++ b/tests/unit/test_stream.py @@ -25,10 +25,10 @@ def setUp(self) -> None: # Initalize the session. self.td_session = TDClient( - client_id=CLIENT_ID, - redirect_uri=REDIRECT_URI, + client_id=CLIENT_ID, + redirect_uri=REDIRECT_URI, credentials_path=JSON_PATH, - account_number = ACCOUNT_NUMBER + account_number=ACCOUNT_NUMBER ) self.stream_session = self.td_session.create_streaming_session() @@ -40,60 +40,109 @@ def test_creates_instance_of_session(self): def test_create_stream_session(self): """Test Creating a new streaming session.""" - + self.assertIsInstance(self.stream_session, TDStreamerClient) def test_create_account_activity(self): """Test subscribing to account activity.""" self.stream_session.account_activity() - self.assertIn('ACCT_ACTIVITY', self.stream_session.data_requests['requests'][0]['service']) + self.assertIn( + 'ACCT_ACTIVITY', self.stream_session.data_requests['requests'][0]['service'] + ) def test_subscribe_level_one_quotes(self): """Test subscribing to Level One Quotes.""" - self.stream_session.level_one_quotes(symbols=['MSFT','AAPL'], fields=list(range(0,1,38))) - self.assertIn('QUOTE', self.stream_session.data_requests['requests'][0]['service']) + self.stream_session.level_one_quotes( + symbols=['MSFT', 'AAPL'], + fields=list(range(0, 1, 38)) + ) + self.assertIn( + 'QUOTE', + self.stream_session.data_requests['requests'][0]['service'] + ) def test_subscribe_level_two_quotes(self): """Test subscribing to Level Two Quotes.""" - self.stream_session.level_two_quotes(symbols=['MSFT','AAPL'], fields=[0,1,2,3]) - self.assertIn('LISTED_BOOK', self.stream_session.data_requests['requests'][0]['service']) + self.stream_session.level_two_quotes( + symbols=['MSFT', 'AAPL'], + fields=[0, 1, 2, 3] + ) + + self.assertIn( + 'LISTED_BOOK', + self.stream_session.data_requests['requests'][0]['service'] + ) def test_subscribe_level_one_options(self): """Test subscribing to Level One Options.""" - self.stream_session.level_one_options(symbols=['AAPL_040920C115'], fields=list(range(0,42))) - self.assertIn('OPTION', self.stream_session.data_requests['requests'][0]['service']) + self.stream_session.level_one_options( + symbols=['AAPL_040920C115'], + fields=list(range(0, 42)) + ) + + self.assertIn( + 'OPTION', + self.stream_session.data_requests['requests'][0]['service'] + ) def test_subscribe_level_one_futures(self): """Test subscribing to Level One Futures.""" # Level One Futures - self.stream_session.level_one_futures(symbols=['/CL'], fields=[0,1,2,3,4,5]) - self.assertIn('FUTURES', self.stream_session.data_requests['requests'][0]['service']) + self.stream_session.level_one_futures( + symbols=['/CL'], + fields=[0, 1, 2, 3, 4, 5] + ) + self.assertIn( + 'FUTURES', + self.stream_session.data_requests['requests'][0]['service'] + ) def test_subscribe_level_one_forex(self): """Test subscribing to Level One Forex.""" # Level One Forex - self.stream_session.level_one_forex(symbols=['EUR/USD'], fields=list(range(0,26))) - self.assertIn('FOREX', self.stream_session.data_requests['requests'][0]['service']) + self.stream_session.level_one_forex( + symbols=['EUR/USD'], + fields=list(range(0, 26)) + ) + self.assertIn( + 'FOREX', + self.stream_session.data_requests['requests'][0]['service'] + ) def test_subscribe_level_one_futures_options(self): """Test subscribing to Level One Futures Options.""" - # Level One Forex - self.stream_session.level_one_futures_options(symbols=['./E1AG20C3220'], fields=list(range(0,36))) - self.assertIn('FUTURES_OPTION', self.stream_session.data_requests['requests'][0]['service']) + # Level One Futures Options. + self.stream_session.level_one_futures_options( + symbols=['./E1AG20C3220'], + fields=list(range(0, 36)) + ) + + self.assertIn( + 'FUTURES_OPTION', + self.stream_session.data_requests['requests'][0]['service'] + ) def test_subscribe_timesale_futures(self): """Test subscribing to Timesale Futures.""" # Timesale Futures - self.stream_session.timesale(service='TIMESALE_FUTURES', symbols=['/ES'], fields=[0,1,2,3,4]) - self.assertIn('TIMESALE_FUTURES', self.stream_session.data_requests['requests'][0]['service']) + self.stream_session.timesale( + service='TIMESALE_FUTURES', + symbols=['/ES'], + fields=[0, 1, 2, 3, 4] + ) + + self.assertIn( + 'TIMESALE_FUTURES', + self.stream_session.data_requests['requests'][0]['service'] + ) def tearDown(self) -> None: """Teardown the Stream Client.""" @@ -101,5 +150,6 @@ def tearDown(self) -> None: self.td_session = None self.stream_session = None + if __name__ == '__main__': unittest.main()