├── .gitignore ├── LICENSE ├── README.md ├── questrade_api ├── __init__.py ├── auth.py ├── enumerations.py ├── questrade.cfg └── questrade.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/* 3 | dist/* 4 | build/* 5 | questrade/__pycache__/* 6 | *.egg-info/ 7 | *.pyc 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Teppen-io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # questrade_api 2 | Python3 Questrade API Wrapper 3 | 4 | ## Installation 5 | * Use `pip`/`pip3`: 6 | 7 | `pip3 install questrade-api` 8 | 9 | ## Getting Started 10 | 1. Familiarise yourself with the [Security documentation](https://www.questrade.com/api/documentation/security) for the Questrade API. 11 | 2. [Generate](https://login.questrade.com/APIAccess/UserApps.aspx) a manual refresh token for your application. 12 | 3. Init the API Wrapper with the refresh token: 13 | 14 | ``` 15 | from questrade_api import Questrade 16 | q = Questrade(refresh_token='XYz1dBlop33lLLuys4Bd') 17 | ``` 18 | **Important:** 19 | A token will be created at `~/.questrade.json` and used for future API calls 20 | * If the token is valid future initiations will not require a refresh token 21 | 22 | ``` 23 | from questrade_api import Questrade 24 | q = Questrade() 25 | ``` 26 | 27 | --- 28 | 29 | ## Using the API 30 | ### [Time](https://www.questrade.com/api/documentation/rest-operations/account-calls/time) 31 | 32 | ``` 33 | q.time 34 | => {'time': '2018-11-16T09:22:27.090000-05:00'} 35 | ``` 36 | 37 | ### [Accounts](https://www.questrade.com/api/documentation/rest-operations/account-calls/accounts) 38 | 39 | ``` 40 | q.accounts 41 | => {'accounts': [{'type': 'Margin', 'number': '123456', 'status': 'Active' ...}]} 42 | ``` 43 | 44 | ### [Account Positions](https://www.questrade.com/api/documentation/rest-operations/account-calls/accounts-id-positions) 45 | 46 | Accepts: `` 47 | 48 | ``` 49 | q.account_positions(123456) 50 | => {'positions': [{'symbol': 'RY.TO', 'symbolId': 34658, 'openQuantity': ...}]} 51 | ``` 52 | 53 | ### [Account Balances](https://www.questrade.com/api/documentation/rest-operations/account-calls/accounts-id-balances) 54 | 55 | Accepts: `` 56 | 57 | ``` 58 | q.account_balances(123456) 59 | => {'perCurrencyBalances': [{'currency': 'CAD', 'cash': 1000, 'marketValue': 0, ...}]} 60 | ``` 61 | 62 | ### [Account Executions](https://www.questrade.com/api/documentation/rest-operations/account-calls/accounts-id-executions) 63 | 64 | Accepts: ``, `startTime=`, `endTime=` 65 | 66 | ``` 67 | q.account_executions(123456) 68 | => {'executions': []} 69 | ``` 70 | 71 | ``` 72 | q.account_executions(123456,startTime='2018-08-01T00:00:00-0') 73 | => {'executions': [{'symbol': 'RY.TO', 'symbolId': 34658, 'quantity': 100, ...}]} 74 | ``` 75 | 76 | ### [Account Orders](https://www.questrade.com/api/documentation/rest-operations/account-calls/accounts-id-orders) 77 | 78 | Accepts: ``, `startTime=`, `endTime=`, `stateFilter=` 79 | 80 | ``` 81 | q.account_orders(123456) 82 | => {'orders': []} 83 | ``` 84 | 85 | ``` 86 | q.account_orders(123456, startTime='2018-08-01T00:00:00-0') 87 | => {'orders': [{'id': 444444, 'symbol': 'RY.TO', 'symbolId': 34658, ...}]} 88 | ``` 89 | 90 | ### [Account Order](https://www.questrade.com/api/documentation/rest-operations/account-calls/accounts-id-orders) 91 | 92 | Accepts: ``, `` 93 | 94 | ``` 95 | q.account_order(123456, 444444) 96 | => {'orders': [{'id': 444444, 'symbol': 'RY.TO', 'symbolId': 34658, 'totalQuantity': 100, ...}]} 97 | ``` 98 | 99 | ### [Account Activities](https://www.questrade.com/api/documentation/rest-operations/account-calls/accounts-id-activities) 100 | 101 | Accepts: ``, `startTime=`, `endTime=` 102 | 103 | ``` 104 | q.account_activities(123456) 105 | => {'activities': []} 106 | ``` 107 | 108 | ``` 109 | q.account_activities(123456, startTime='2018-11-01T00:00:00-0') 110 | => {'activities': []} 111 | ``` 112 | 113 | ### [Symbol](https://www.questrade.com/api/documentation/rest-operations/market-calls/symbols-id) 114 | 115 | Accepts: `` 116 | 117 | ``` 118 | q.symbol(34659) 119 | => {'symbols': [{'symbol': 'RY.TO 'symbolId': 34658, 'prevDayClosePrice': ...}]} 120 | ``` 121 | 122 | ### [Symbols](https://www.questrade.com/api/documentation/rest-operations/market-calls/symbols-id) 123 | 124 | Accepts: `ids=','`, `names=','` 125 | 126 | ``` 127 | q.symbols(ids='34658,9339') 128 | => {'symbols': [{'symbol': 'RY.TO', 'symbolId': 34658, 'prevDayClosePrice': ..}]} 129 | ``` 130 | 131 | ``` 132 | q.symbols(names='RY.TO,BNS.TO') 133 | => {'symbols': [{'symbol': 'RY.TO', 'symbolId': 34658, 'prevDayClosePrice': ..}]} 134 | ``` 135 | 136 | ### [Symbols Search](https://www.questrade.com/api/documentation/rest-operations/market-calls/symbols-search) 137 | 138 | Accepts: `prefix=''`, `offset=` 139 | 140 | ``` 141 | q.symbols_search(prefix='RY.TO') 142 | => {'symbols': [{'symbol': 'RY.TO', 'symbolId': 34658, 'description': ...}]} 143 | ``` 144 | 145 | ``` 146 | q.symbols_search(prefix='RY', offset=5) 147 | {'symbols': [{'symbol': 'RY.PRE.TO', 'symbolId': 34700, 'description': ...}]} 148 | ``` 149 | 150 | ### [Symbol Options](https://www.questrade.com/api/documentation/rest-operations/market-calls/symbols-id-options) 151 | 152 | Accepts: `` 153 | 154 | ``` 155 | q.symbol_options(34658) 156 | => {'optionChain': [{'expiryDate': '2018-11-16T00:00:00.000000-05:00', 'description': ... }]} 157 | ``` 158 | 159 | ### [Markets](https://www.questrade.com/api/documentation/rest-operations/market-calls/markets) 160 | 161 | ``` 162 | q.markets 163 | => {'markets': [{'name': 'TSX', 'tradingVenues': ['TSX', 'ALPH', 'CXC', ... }]} 164 | ``` 165 | 166 | ### [Markets Quote](https://www.questrade.com/api/documentation/rest-operations/market-calls/markets-quotes-id) 167 | 168 | Accepts: `` 169 | 170 | ``` 171 | q.markets_quote(34658) 172 | => {'quotes': [{'symbol': 'RY.TO', 'symbolId': 34658, 'tier': ... }]} 173 | ``` 174 | 175 | ### [Markets Quotes](https://www.questrade.com/api/documentation/rest-operations/market-calls/markets-quotes-id) 176 | 177 | Accepts: `ids=','` 178 | 179 | ``` 180 | q.markets_quotes(ids='34658,9339') 181 | => {'quotes': [{'symbol': 'RY.TO', 'symbolId': 34658, 'tier': ... }]} 182 | ``` 183 | 184 | ### [Markets Options](https://www.questrade.com/api/documentation/rest-operations/market-calls/markets-quotes-options) 185 | 186 | Accepts: `optionIds=`, `filters=` 187 | 188 | ``` 189 | q.markets_options(optionIds=[ 190 | 23615873, 191 | 23615874 192 | ]) 193 | => {'optionQuotes': [{'underlying': 'RY.TO', 'underlyingId': 34658, 'symbol': 'RY30Nov18 ..}]} 194 | ``` 195 | 196 | ``` 197 | q.markets_options(filters=[ 198 | { 199 | "optionType": "Call", 200 | "underlyingId": 34658, 201 | "expiryDate": "2018-11-30T00:00:00.000000-05:00", 202 | "minstrikePrice": 90, 203 | "maxstrikePrice": 100 204 | } 205 | ]) 206 | => {'optionQuotes': [{'underlying': 'RY.TO', 'underlyingId': 34658, 'symbol': 'RY30Nov18 ..}]} 207 | ``` 208 | 209 | ### [Markets Strategies](https://www.questrade.com/api/documentation/rest-operations/market-calls/markets-quotes-strategies) 210 | 211 | Accepts: `variants=` 212 | 213 | ``` 214 | q.markets_strategies(variants=[ 215 | { 216 | "variantId": 1, 217 | "strategy": "Custom", 218 | "legs": [ 219 | { 220 | "symbolId": 23545340, 221 | "ratio": 10, 222 | "action": "Buy" 223 | }, 224 | { 225 | "symbolId": 23008592, 226 | "ratio": 10, 227 | "action": "Sell" 228 | } 229 | ] 230 | } 231 | ]) 232 | => {'strategyQuotes': [{'variantId': 1, 'bidPrice': None, 'askPrice': None, 'underlying': 'RY.TO' ...}]} 233 | ``` 234 | 235 | ### [Markets Candles](https://www.questrade.com/api/documentation/rest-operations/market-calls/markets-candles-id) 236 | 237 | Accepts: ``, `startTime=`, `endTime=`, `interval=` 238 | 239 | ``` 240 | q.markets_candles(34658, interval='FiveMinutes') 241 | => {'candles': [{'start': '2018-11-16T09:30:00.583000-05:00', 'end': '2018-11-16T ..}]} 242 | ``` 243 | -------------------------------------------------------------------------------- /questrade_api/__init__.py: -------------------------------------------------------------------------------- 1 | from .questrade import Questrade # noqa 2 | from .auth import Auth # noqa 3 | -------------------------------------------------------------------------------- /questrade_api/auth.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import time 4 | from urllib import request 5 | 6 | TOKEN_PATH = os.path.expanduser('~/.questrade.json') 7 | 8 | 9 | class Auth: 10 | def __init__(self, **kwargs): 11 | if 'config' in kwargs: 12 | self.config = kwargs['config'] 13 | else: 14 | raise Exception('No config supplied') 15 | if 'token_path' in kwargs: 16 | self.token_path = kwargs['token_path'] 17 | else: 18 | self.token_path = TOKEN_PATH 19 | if 'refresh_token' in kwargs: 20 | self.__refresh_token(kwargs['refresh_token']) 21 | 22 | def __read_token(self): 23 | try: 24 | with open(self.token_path) as f: 25 | str = f.read() 26 | return json.loads(str) 27 | except IOError: 28 | raise('No token provided and none found at {}'.format(TOKEN_PATH)) 29 | 30 | def __write_token(self, token): 31 | with open(self.token_path, 'w') as f: 32 | json.dump(token, f) 33 | os.chmod(self.token_path, 0o600) 34 | 35 | def __refresh_token(self, token): 36 | req_time = int(time.time()) 37 | r = request.urlopen(self.config['Auth']['RefreshURL'].format(token)) 38 | if r.getcode() == 200: 39 | token = json.loads(r.read().decode('utf-8')) 40 | token['expires_at'] = str(req_time + token['expires_in']) 41 | self.__write_token(token) 42 | 43 | def __get_valid_token(self): 44 | try: 45 | self.token_data 46 | except AttributeError: 47 | self.token_data = self.__read_token() 48 | finally: 49 | if time.time() + 60 < int(self.token_data['expires_at']): 50 | return self.token_data 51 | else: 52 | self.__refresh_token(self.token_data['refresh_token']) 53 | self.token_data = self.__read_token() 54 | return self.token_data 55 | 56 | @property 57 | def token(self): 58 | return self.__get_valid_token() 59 | -------------------------------------------------------------------------------- /questrade_api/enumerations.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Currency(Enum): 5 | USD = 'USD' 6 | CAD = 'CAD' 7 | 8 | 9 | class Exchange(Enum): 10 | TSX = 'TSX' 11 | TSXV = 'TSXV' 12 | CNSX = 'CNSX' 13 | MX = 'MX' 14 | NASDAQ = 'NASDAQ' 15 | NYSE = 'NYSE' 16 | AMEX = 'AMEX' 17 | ARCA = 'ARCA' 18 | OPRA = 'OPRA' 19 | PinkSheets = 'PinkSheets' 20 | OTCBB = 'OTCBB' 21 | 22 | 23 | class AccountType(Enum): 24 | Cash = 'Cash' 25 | Margin = 'Margin' 26 | TFSA = 'TFSA' 27 | RRSP = 'RRSP' 28 | SRRSP = 'SRRSP' 29 | LRRSP = 'LRRSP' 30 | LIRA = 'LIRA' 31 | LIF = 'LIF' 32 | RIF = 'RIF' 33 | SRIF = 'SRIF' 34 | LRIF = 'LRIF' 35 | RRIF = 'RRIF' 36 | PRIF = 'PRIF' 37 | RESP = 'RESP' 38 | FRESP = 'FRESP' 39 | 40 | 41 | class ClientAccountType(Enum): 42 | Individual = 'Individual' 43 | Joint = 'Joint' 44 | InformalTrust = 'InformalTrust' 45 | Corporation = 'Corporation' 46 | InvestmentClub = 'InvestmentClub' 47 | FormalTrust = 'FormalTrust' 48 | Partnership = 'Partnership' 49 | SoleProprietorship = 'SoleProprietorship' 50 | Family = 'Family' 51 | JointAndInformalTrust = 'JointAndInformalTrust' 52 | Institution = 'Institution' 53 | 54 | 55 | class AccountStatus(Enum): 56 | Active = 'Active' 57 | SuspendedClosed = 'Suspended(Closed)' 58 | Suspended = 'Suspended(View Only)' 59 | LiquidateOnly = 'LiquidateOnly' 60 | Closed = 'Closed' 61 | 62 | 63 | class TickType(Enum): 64 | Up = 'Up' 65 | Down = 'Down' 66 | Equal = 'Equal' 67 | 68 | 69 | class OptionType(Enum): 70 | Call = 'Call' 71 | Put = 'Put' 72 | 73 | 74 | class OptionDurationType(Enum): 75 | Weekly = 'Weekly' 76 | Monthly = 'Monthly' 77 | Quarterly = 'Quarterly' 78 | LEAP = 'LEAP' 79 | 80 | 81 | class OptionExerciseType(Enum): 82 | American = 'American' 83 | European = 'European' 84 | 85 | 86 | class SecurityType(Enum): 87 | Stock = 'Stock' 88 | Option = 'Option' 89 | Bond = 'Bond' 90 | Right = 'Right' 91 | Gold = 'Gold' 92 | MutualFund = 'MutualFund' 93 | Index = 'Index' 94 | 95 | 96 | class OrderStateFilterType(Enum): 97 | All = 'All' 98 | Open = 'Open' 99 | Closed = 'Closed' 100 | 101 | 102 | class OrderAction(Enum): 103 | Buy = 'Buy' 104 | Sell = 'Sell' 105 | 106 | 107 | class OrderSide(Enum): 108 | Buy = 'Buy' 109 | Sell = 'Sell' 110 | Short = 'Short' 111 | Cov = 'Cov' 112 | BTO = 'BTO' 113 | STC = 'STC' 114 | STO = 'STO' 115 | BTC = 'BTC' 116 | 117 | 118 | class OrderType(Enum): 119 | Market = 'Market' 120 | Limit = 'Limit' 121 | Stop = 'Stop' 122 | StopLimit = 'StopLimit' 123 | TrailStopInPercentage = 'TrailStopInPercentage' 124 | TrailStopInDollar = 'TrailStopInDollar' 125 | TrailStopLimitInPercentage = 'TrailStopLimitInPercentage' 126 | TrailStopLimitInDollar = 'TrailStopLimitInDollar' 127 | LimitOnOpen = 'LimitOnOpen' 128 | LimitOnClose = 'LimitOnClose' 129 | 130 | 131 | class OrderTimeInForce(Enum): 132 | Day = 'Day' 133 | GoodTillCanceled = 'GoodTillCanceled' 134 | GoodTillExtendedDay = 'GoodTillExtendedDay' 135 | GoodTillDate = 'GoodTillDate' 136 | ImmediateOrCancel = 'ImmediateOrCancel' 137 | FillOrKill = 'FillOrKill' 138 | 139 | 140 | class OrderState(Enum): 141 | Failed = 'Failed' 142 | Pending = 'Pending' 143 | Accepted = 'Accepted' 144 | Rejected = 'Rejected' 145 | CancelPending = 'CancelPending' 146 | Canceled = 'Canceled' 147 | PartialCanceled = 'PartialCanceled' 148 | Partial = 'Partial' 149 | Executed = 'Executed' 150 | ReplacePending = 'ReplacePending' 151 | Replaced = 'Replaced' 152 | Stopped = 'Stopped' 153 | Suspended = 'Suspended' 154 | Expired = 'Expired' 155 | Queued = 'Queued' 156 | Triggered = 'Triggered' 157 | Activated = 'Activated' 158 | PendingRiskReview = 'PendingRiskReview' 159 | ContingentOrder = 'ContingentOrder' 160 | 161 | 162 | class HistoricalDataGranularity(Enum): 163 | OneMinute = 'OneMinute' 164 | TwoMinutes = 'TwoMinutes' 165 | ThreeMinutes = 'ThreeMinutes' 166 | FourMinutes = 'FourMinutes' 167 | FiveMinutes = 'FiveMinutes' 168 | TenMinutes = 'TenMinutes' 169 | FifteenMinutes = 'FifteenMinutes' 170 | TwentyMinutes = 'TwentyMinutes' 171 | HalfHour = 'HalfHour' 172 | OneHour = 'OneHour' 173 | TwoHours = 'TwoHours' 174 | FourHours = 'FourHours' 175 | OneDay = 'OneDay' 176 | OneWeek = 'OneWeek' 177 | OneMonth = 'OneMonth' 178 | OneYear = 'OneYear' 179 | 180 | 181 | class OrderClass(Enum): 182 | Primary = 'Primary' 183 | Limit = 'Limit' 184 | StopLoss = 'StopLoss' 185 | 186 | 187 | class StrategyTypes(Enum): 188 | CoveredCall = 'CoveredCall' 189 | MarriedPuts = 'MarriedPut' 190 | VerticalCallSpread = 'VerticalCall' 191 | VerticalPutSpread = 'VerticalPut' 192 | CalendarCallSpread = 'CalendarCall' 193 | CalendarPutSpread = 'CalendarPut' 194 | DiagonalCallSpread = 'DiagonalCall' 195 | DiagonalPutSpread = 'DiagonalPut' 196 | Collar = 'Collar' 197 | Straddle = 'Straddle' 198 | Strangle = 'Strangle' 199 | ButterflyCall = 'ButterflyCall' 200 | ButterflyPut = 'ButterflyPut' 201 | IronButterfly = 'IronButterfly' 202 | CondorCall = 'Condor' 203 | Custom = 'Custom' 204 | -------------------------------------------------------------------------------- /questrade_api/questrade.cfg: -------------------------------------------------------------------------------- 1 | [Settings] 2 | Version = v1 3 | 4 | [Auth] 5 | RefreshURL = https://login.questrade.com/oauth2/token?grant_type=refresh_token&refresh_token={} 6 | 7 | [API] 8 | Time = /time 9 | Accounts = /accounts 10 | AccountPositions = /accounts/{}/positions 11 | AccountBalances = /accounts/{}/balances 12 | AccountExecutions = /accounts/{}/executions 13 | AccountOrders = /accounts/{}/orders 14 | AccountOrder = /accounts/{}/orders/{} 15 | AccountActivities = /accounts/{}/activities 16 | Symbol = /symbols/{} 17 | SymbolOptions = /symbols/{}/options 18 | Symbols = /symbols 19 | SymbolsSearch = /symbols/search 20 | Markets = /markets 21 | MarketsQuote = /markets/quotes/{} 22 | MarketsQuotes = /markets/quotes 23 | MarketsOptions = /markets/quotes/options 24 | MarketsStrategies = /markets/quotes/strategies 25 | MarketsCandles = /markets/candles/{} 26 | -------------------------------------------------------------------------------- /questrade_api/questrade.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from datetime import datetime, timedelta 4 | import configparser 5 | import urllib 6 | from questrade_api.auth import Auth 7 | 8 | CONFIG_PATH = os.path.join(os.path.abspath( 9 | os.path.dirname(__file__)), 'questrade.cfg') 10 | 11 | 12 | class Questrade: 13 | def __init__(self, **kwargs): 14 | if 'config' in kwargs: 15 | self.config = self.__read_config(kwargs['config']) 16 | else: 17 | self.config = self.__read_config(CONFIG_PATH) 18 | 19 | auth_kwargs = {x: y for x, y in kwargs.items() if x in 20 | ['token_path', 'refresh_token']} 21 | 22 | self.auth = Auth(**auth_kwargs, config=self.config) 23 | 24 | def __read_config(self, fpath): 25 | config = configparser.ConfigParser() 26 | with open(os.path.expanduser(fpath)) as f: 27 | config.read_file(f) 28 | return config 29 | 30 | @property 31 | def __base_url(self): 32 | return self.auth.token['api_server'] + self.config['Settings']['Version'] 33 | 34 | def __build_get_req(self, url, params): 35 | if params: 36 | url = self.__base_url + url + '?' + urllib.parse.urlencode(params) 37 | return urllib.request.Request(url) 38 | else: 39 | return urllib.request.Request(self.__base_url + url) 40 | 41 | def __get(self, url, params=None): 42 | req = self.__build_get_req(url, params) 43 | req.add_header( 44 | 'Authorization', 45 | self.auth.token['token_type'] + ' ' + 46 | self.auth.token['access_token'] 47 | ) 48 | try: 49 | r = urllib.request.urlopen(req) 50 | return json.loads(r.read().decode('utf-8')) 51 | except urllib.error.HTTPError as e: 52 | return json.loads(e.read().decode('utf-8')) 53 | 54 | def __build_post_req(self, url, params): 55 | url = self.__base_url + url 56 | return urllib.request.Request(url, data=json.dumps(params).encode('utf8')) 57 | 58 | def __post(self, url, params): 59 | req = self.__build_post_req(url, params) 60 | req.add_header( 61 | 'Authorization', 62 | self.auth.token['token_type'] + ' ' + 63 | self.auth.token['access_token'] 64 | ) 65 | try: 66 | r = urllib.request.urlopen(req) 67 | return json.loads(r.read().decode('utf-8')) 68 | except urllib.error.HTTPError as e: 69 | return json.loads(e.read().decode('utf-8')) 70 | 71 | @property 72 | def __now(self): 73 | return datetime.now().astimezone().isoformat('T') 74 | 75 | def __days_ago(self, d): 76 | now = datetime.now().astimezone() 77 | return (now - timedelta(days=d)).isoformat('T') 78 | 79 | @property 80 | def time(self): 81 | return self.__get(self.config['API']['time']) 82 | 83 | @property 84 | def accounts(self): 85 | return self.__get(self.config['API']['Accounts']) 86 | 87 | def account_positions(self, id): 88 | return self.__get(self.config['API']['AccountPositions'].format(id)) 89 | 90 | def account_balances(self, id): 91 | return self.__get(self.config['API']['AccountBalances'].format(id)) 92 | 93 | def account_executions(self, id, **kwargs): 94 | return self.__get(self.config['API']['AccountExecutions'].format(id), kwargs) 95 | 96 | def account_orders(self, id, **kwargs): 97 | if 'ids' in kwargs: 98 | kwargs['ids'] = kwargs['ids'].replace(' ', '') 99 | return self.__get(self.config['API']['AccountOrders'].format(id), kwargs) 100 | 101 | def account_order(self, id, order_id): 102 | return self.__get(self.config['API']['AccountOrder'].format(id, order_id)) 103 | 104 | def account_activities(self, id, **kwargs): 105 | if 'startTime' not in kwargs: 106 | kwargs['startTime'] = self.__days_ago(1) 107 | if 'endTime' not in kwargs: 108 | kwargs['endTime'] = self.__now 109 | return self.__get(self.config['API']['AccountActivities'].format(id), kwargs) 110 | 111 | def symbol(self, id): 112 | return self.__get(self.config['API']['Symbol'].format(id)) 113 | 114 | def symbols(self, **kwargs): 115 | if 'ids' in kwargs: 116 | kwargs['ids'] = kwargs['ids'].replace(' ', '') 117 | return self.__get(self.config['API']['Symbols'].format(id), kwargs) 118 | 119 | def symbols_search(self, **kwargs): 120 | return self.__get(self.config['API']['SymbolsSearch'].format(id), kwargs) 121 | 122 | def symbol_options(self, id): 123 | return self.__get(self.config['API']['SymbolOptions'].format(id)) 124 | 125 | @property 126 | def markets(self): 127 | return self.__get(self.config['API']['Markets']) 128 | 129 | def markets_quote(self, id): 130 | return self.__get(self.config['API']['MarketsQuote'].format(id)) 131 | 132 | def markets_quotes(self, **kwargs): 133 | if 'ids' in kwargs: 134 | kwargs['ids'] = kwargs['ids'].replace(' ', '') 135 | return self.__get(self.config['API']['MarketsQuotes'], kwargs) 136 | 137 | def markets_options(self, **kwargs): 138 | return self.__post(self.config['API']['MarketsOptions'], kwargs) 139 | 140 | def markets_strategies(self, **kwargs): 141 | return self.__post(self.config['API']['MarketsStrategies'], kwargs) 142 | 143 | def markets_candles(self, id, **kwargs): 144 | if 'startTime' not in kwargs: 145 | kwargs['startTime'] = self.__days_ago(1) 146 | if 'endTime' not in kwargs: 147 | kwargs['endTime'] = self.__now 148 | return self.__get(self.config['API']['MarketsCandles'].format(id), kwargs) 149 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 160 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name='questrade-api', 8 | version='1.0.3', 9 | description='Python3 Questrade API Wrapper', 10 | long_description=long_description, 11 | long_description_content_type="text/markdown", 12 | url='https://github.com/tgardiner/questrade_api', 13 | author='Tom Gardiner', 14 | author_email='tom@teppen.io', 15 | license='MIT', 16 | packages=setuptools.find_packages(), 17 | package_data={ 18 | 'questrade_api': ['questrade.cfg'], 19 | }, 20 | python_requires='>=3', 21 | classifiers=[ 22 | "Programming Language :: Python :: 3", 23 | "License :: OSI Approved :: MIT License", 24 | "Operating System :: OS Independent", 25 | ] 26 | ) 27 | --------------------------------------------------------------------------------