Giter VIP home page Giter VIP logo

qtrade's Introduction

All tests passing

Qtrade

This is a very basic Python 3.8+ wrapper for the Questrade API, a Canadian low cost broker.

Installation

This package is available via PyPI and can be installed via the command

pip install qtrade

Usage

For an overview of the package API, please take a look at the docs. The main class of the package is called Questrade and houses most of the functionality provided by the package. Below are a few examples for possible use cases.

Token management

The central class can be initialized via

from qtrade import Questrade

qtrade = Questrade(access_code='<access_code>')

where <access_code> is the token that one gets from the Questrade API portal. It is called access_code since this initial token is used to get the full token data that will include

{'access_token': <access_token>,
 'api_server': '<api_url>',
 'expires_in': 1234,
 'refresh_token': <refresh_token>,
 'token_type': 'Bearer'}

The first call initializes the class and the second call gets the full token.

Another way to initialize the class is to use a token yaml-file via:

qtrade = Questrade(token_yaml='<yaml_path>')

where the yaml-file would have the general form

access_token: <access_token>
api_server: <api_url>
expires_in: 1234
refresh_token: <refresh_token>
token_type: Bearer

If the token is expired, one can use

qtrade.refresh_access_token(from_yaml=True)

to refresh the access token using the saved refresh token.

Once the tokens are set correctly, I have currently added methods to get ticker quotes, the current status of all positions in any Questrade account that is associated with the tokens, any account activities such as trades and dividend payments as well as historical data for tickers that are supported by Questrade.

Basic functionality

There currently exists some basic functionality to get stock information via

aapl, amzn = qtrade.ticker_information(['AAPL', 'AMZN'])

and current stock quotes can be obtained via

aapl, amzn = qtrade.get_quote(['AAPL', 'AMZN'])

In addition, one can get historical stock quotes via

aapl_history = qtrade.get_historical_data('AAPL', '2018-08-01', '2018-08-21','OneHour')

Here, the last input parameter is the interval between quotes. Another option could be 'OneDay'. For more options, see the Questrade API description.

Account information

In addition, the Questrade API gives access to account information about the accounts connected to the token. The accounts IDs can be accessed via

account_ids = qtrade.get_account_id()

By using the correct account ID, one can get the positions of the accounts via

positions = qtrade.get_account_positions(account_id=123456)

Finally, there exists a method to get all account activities (trades, dividends received, etc.) of an account in a certain time frame via

activities = qtrade.get_account_activities(123456, '2018-08-01', '2018-08-16')

Contributors

Contributions are always appreciated! For example:

  • open an issue for a missing feature or a bug
  • give feedback about existing functionality
  • make suggestions for improvements
  • submit a PR with a new feature (though reaching out would be appreciated)
  • etc.

There is a test suite that can be run via python -m pytest. This project uses pre-commit and black, flake8 and isort which takes care of automatic code formatting and linting. When setting up the development environment, run pre-commit install to set up the hook. This will run all the linting automatically when committing code changes.

Disclaimer

I am in no way affiliated with Questrade and using this API wrapper is licensed via the MIT license.

qtrade's People

Contributors

ajhpark avatar jborchma avatar mmikitka avatar r-luo avatar surohitt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

qtrade's Issues

SSLCertVerificationError & MaxRetryError & SSLError

Hi Jan,

Recently, I have encountered errors related to SSLCertVerification and MaxRetry below, and I am not sure what went wrong from my side. Specifically, I get these errors when I call get_account_id method.


SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)

During handling of the above exception, another exception occurred:

MaxRetryError Traceback (most recent call last)
~\anaconda3\lib\site-packages\requests\adapters.py in send(self, request, stream, timeout, verify, cert, proxies)
438 if not chunked:
--> 439 resp = conn.urlopen(
440 method=request.method,

~\anaconda3\lib\site-packages\urllib3\connectionpool.py in urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
754
--> 755 retries = retries.increment(
756 method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]

~\anaconda3\lib\site-packages\urllib3\util\retry.py in increment(self, method, url, response, error, _pool, _stacktrace)
573 if new_retry.is_exhausted():
--> 574 raise MaxRetryError(_pool, url, error or ResponseError(cause))
575

MaxRetryError: HTTPSConnectionPool(host='api05.iq.questrade.com', port=443): Max retries exceeded with url: /v1/accounts (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)')))

During handling of the above exception, another exception occurred:

SSLError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_9912/2462350887.py in
1 acctData = {}
----> 2 acctNums = qbot.get_account_id()
3 ACCOUNT_TYPE = ESHINHW_ACCOUNT_TYPE
4 for aNum in acctNums:
5 if aNum in ACCOUNT_TYPE:

~\Desktop\GitHub\questrade-portfolio-manager\utils\qtrade.py in get_account_id(self)
221 """
222 log.info("Getting account ID...")
--> 223 response: Dict[str, List[Dict[str, int]]] = self._send_message("get", "accounts")
224
225 account_id = []

~\Desktop\GitHub\questrade-portfolio-manager\utils\qtrade.py in _send_message(self, method, endpoint, params, data, json)
89 log.error("Access token not set...")
90 raise Exception("Access token not set...")
---> 91 resp = self.session.request(method, url, params=params, data=data, json=json, timeout=30)
92 resp.raise_for_status()
93 return resp.json()

~\anaconda3\lib\site-packages\requests\sessions.py in request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
540 }
541 send_kwargs.update(settings)
--> 542 resp = self.send(prep, **send_kwargs)
543
544 return resp

~\anaconda3\lib\site-packages\requests\sessions.py in send(self, request, **kwargs)
653
654 # Send the request
--> 655 r = adapter.send(request, **kwargs)
656
657 # Total elapsed time of the request (approximately)

~\anaconda3\lib\site-packages\requests\adapters.py in send(self, request, stream, timeout, verify, cert, proxies)
512 if isinstance(e.reason, _SSLError):
513 # This branch is for urllib3 v1.22 and later.
--> 514 raise SSLError(e, request=request)
515
516 raise ConnectionError(e, request=request)

SSLError: HTTPSConnectionPool(host='api05.iq.questrade.com', port=443): Max retries exceeded with url: /v1/accounts (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)')))

Is there anything I can change on my side to fix those errors, or is it a problem from the Questrade side?

Thanks!

Improve token management

The token management could definitely be improved. Maybe it would work in conjunction with the requests.Session object. Definitely room for improvement.

Debugging a 400 bad request and clarifying the access code

Hello Jan. First, thanks for creating this package, and I look forward to using it.

I was not able to obtain an access token. Can you please clarify the initialization workflow and where the values are sourced from? I did the following:

  1. Created a Personal App at https://login.questrade.com/APIAccess/UserApps.aspx
  2. Clicked the "+ New device" link to create a new manually authorized device
  3. Clicked the "Generate token" link to create a new token (call it MY_TOKEN=1234567890ABCDEFG)
  4. Passed this token into qtrade.Questrade(access_code=MY_TOKEN)

I then get the following traceback

Traceback (most recent call last):
  File "./options.py", line 11, in <module>
    qtrade = Questrade(access_code=access_code)
  File "/app/qtrade/qtrade/questrade.py", line 38, in __init__
    self.get_access_token()
  File "/app/qtrade/qtrade/questrade.py", line 83, in get_access_token
    data.raise_for_status()
  File "/usr/local/lib/python3.7/site-packages/requests/models.py", line 940, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://login.questrade.com/oauth2/token?grant_type=refresh_token&refresh_token=1234567890ABCDEFG

Add more GH actions steps

It would be nice to automatically build the sphinx docs from GH actions when I add a new release. Also pushing the new release to pypi would be nice.

Going one step further, a push to master could automatically create the release (release message could be found from the last PR). But that's probably optional.

"400 Client Error: Bad Request for url" when using "get_historical_data"

Environment: conda python==3.8 environment with qtrade and ipython installed

Issue:
'OneDay' works with get_historical_data:

uvxy = q.get_historical_data('UVXY', '2019-07-01', '2019-07-02', 'OneDay')

but changing 'OneDay' to 'OneHour' causes error:

>>> uvxy = q.get_historical_data('UVXY', '2019-07-01', '2019-07-02', 'OneHour')                                                 
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
<ipython-input-22-a0637f02abb7> in <module>
----> 1 uvxy = q.get_historical_data('UVXY', '2019-07-01', '2019-07-02', 'OneHour')

~/Documents/projects/qtrade/qtrade/questrade.py in get_historical_data(self, ticker, start_date, end_date, interval)
    448         )
    449 
--> 450         response = self._send_message(
    451             "get", "markets/candles/" + str(ids), params=payload
    452         )

~/Documents/projects/qtrade/qtrade/questrade.py in _send_message(self, method, endpoint, params, data, json)
     83             method, url, params=params, data=data, json=json, timeout=30
     84         )
---> 85         resp.raise_for_status()
     86         return resp.json()
     87 

~/anaconda3/envs/qtrade/lib/python3.8/site-packages/requests/models.py in raise_for_status(self)
    939 
    940         if http_error_msg:
--> 941             raise HTTPError(http_error_msg, response=self)
    942 
    943     def close(self):

HTTPError: 400 Client Error: Bad Request for url: https://api06.iq.questrade.com/v1/markets/candles/22992135?startTime=2019-07-01T00%3A00%3A00-05%3A00&endTime=2019-07-02T00%3A00%3A00-05%3A00&interval=OneHour

Add method to convert to pandas?

I'm assuming most of the usages for this package would be for analytics, do you want to include analytical utilities such as convert_to_pandas etc. in this package or do you think those should be separate?

Unclosed socket when doing testing with unittest

I'm getting the following warning when testing the API:
ResourceWarning: unclosed <ssl.SSLSocket fd=1104, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.5.66', 50373), raddr=('204.89.196.131', 443)>

The reason is that the Questrade class is missing a session closing statement. Please add the following:

class Questrade:
    ...
    def __del__(self):
        self.session.close()

Thank you,

Expand pre-commit

I think it would make sense to include things like flake8, isort and the generic whitespace linters from pre-commit in the yaml-file.

403 Client Error - get_historical_data

Firstly, thanks @jborchma for this library.

I am able to download data such as ticker information, account info etc, but when i try to download historical data i get the below error:

HTTPError: 403 Client Error: Forbidden for url: https://api02.iq.questrade.com/v1/markets/candles/36261155?startTime=2019-07-01T00%3A00%3A00-05%3A00&endTime=2019-07-02T00%3A00%3A00-05%3A00&interval=OneDay

Does this mean i do not have access to historical market data?

Anyone else get this issue?

Thanks

I believe "refresh_access_token" stopped working today.

I hope it's not me; but it worked yesterday and today it never returns. Even though the token was not expired I got a new one; same result.
I'm not sure how I could provide more info about this.
Code:
from qtrade import Questrade
MyQtrade = Questrade(token_yaml="access_token.yml")
MyQtrade.refresh_access_token(from_yaml=True)
print(MyQtrade.ticker_information('IBM'))

I run each line in the interpreter and "refresh_access_token" does not return.
I stopped it with Ctrl-C and I got the following:
`

MyQtrade.refresh_access_token(from_yaml=True)
Traceback (most recent call last):
File "", line 1, in
File "C:\Users\Cat\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\qtrade\questrade.py", line 194, in refresh_access_token
data = requests.get(url)
File "C:\Users\Cat\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\requests\api.py", line 75, in get
return request('get', url, params=params, **kwargs)
File "C:\Users\Cat\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\requests\api.py", line 61, in request
return session.request(method=method, url=url, **kwargs)
File "C:\Users\Cat\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\requests\sessions.py", line 529, in request
resp = self.send(prep, **send_kwargs)
File "C:\Users\Cat\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\requests\sessions.py", line 645, in send
r = adapter.send(request, **kwargs)
File "C:\Users\Cat\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\requests\adapters.py", line 440, in send
resp = conn.urlopen(
File "C:\Users\Cat\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\urllib3\connectionpool.py", line 703, in urlopen
httplib_response = self._make_request(
File "C:\Users\Cat\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\urllib3\connectionpool.py", line 449, in _make_request
six.raise_from(e, None)
File "", line 3, in raise_from
File "C:\Users\Cat\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\urllib3\connectionpool.py", line 444, in _make_request
httplib_response = conn.getresponse()
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.1264.0_x64__qbz5n2kfra8p0\lib\http\client.py", line 1374, in getresponse
response.begin()
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.1264.0_x64__qbz5n2kfra8p0\lib\http\client.py", line 318, in begin
version, status, reason = self._read_status()
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.1264.0_x64__qbz5n2kfra8p0\lib\http\client.py", line 279, in _read_status
line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.1264.0_x64__qbz5n2kfra8p0\lib\socket.py", line 705, in readinto
return self._sock.recv_into(b)
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.1264.0_x64__qbz5n2kfra8p0\lib\ssl.py", line 1273, in recv_into
return self.read(nbytes, buffer)
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.1264.0_x64__qbz5n2kfra8p0\lib\ssl.py", line 1129, in read
return self._sslobj.read(len, buffer)
KeyboardInterrupt

`
Anything I'm doing wrong, or that I can do to fix this, please?

Improve Error messages

Currently, the error messages are not very informative. Add much clearer error messages that actually tell the user what went wrong.

Move to Github actions

I think with all the changes to Travis recently, it might be time to move to Github actions before running out of credits.

Add logging

As the title says: Add logging functionality.

submit ordre

the submit_order is not working with me:
the http status code is 400 (bad request). Can you please give us an example ? I am using a partner app (Medved Trader)

oauth2 token only works once

I believe QT made some changes in the last year and things might have been broken. So, these are my steps:

Basically, I have to create a new token for each run.

Am I doing something wrong?

Add sphinx documentation

As the title says, add sphinx documentation and look into automatically deploying it to gh-pages using travis.

Add tests

Like the title says, add testing.

  • utilities
  • qtrade class

Export L1

Add a method to connect to L1 streaming and show and eventually export to csv all the transaction

get account balances

Hi,
When I try to call get_account_balances I get the following error: AttributeError: 'Questrade' object has no attribute 'get_account_balances'

In the questrade.py file I see the function is in there, not sure why Im not able to call it. Sorry If Im missing something, im new to python.

Thanks

Manage multiple accounts from different login IDs

Hello!

I am just wondering if qtrade can work with multiple accounts from different login IDs.

In my situation, I have no issue with using qtrade, but I want to manage other people's accounts with different API keys.

Based on my understanding, access_token.yml only works with one ID, and I have to delete and recreate it for new ID.

Is this correct and are there ways to access different accounts from different IDs?

Thanks,

Extend readme

The readme should have a couple of more examples beyond just showing the token handling.

Suggestion: get account balance feature

Hi, I've enjoyed using your Questrade wrapper to perform analytic works on my accounts at Questrade. Just one suggestion, I hope there's a method which retrieves account balance data such as cash amount, total equity, and total market value by currencies. I figured in my own way to retrieve my balance data like below, but having this feature officially on your wrapper would be quite useful.

image

(^ Not sure if this is a safe and right way to get the data from API, but it works fine with me so far)

Thanks!

json.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)

*** Error Output Below ***

File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/decoder.py", line 353, in raw_decode
  obj, end = self.scan_once(s, idx)

builtins.StopIteration: 1

During handling of the above exception, another exception occurred:

 File "/dowload_questrade_data/questrade/questrade_play.py", line 4, in <module>
  q = Questrade(refresh_token='<my token code>')
File "/usr/local/lib/python3.7/site-packages/questrade_api/questrade.py", line 22, in __init__
  self.auth = Auth(**auth_kwargs, config=self.config)
File "/usr/local/lib/python3.7/site-packages/questrade_api/auth.py", line 20, in __init__
  self.__refresh_token(kwargs['refresh_token'])
File "/usr/local/lib/python3.7/site-packages/questrade_api/auth.py", line 39, in __refresh_token
  token = json.loads(r.read().decode('utf-8'))
File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 348, in loads
  return _default_decoder.decode(s)
File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/decoder.py", line 337, in decode
  obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/decoder.py", line 355, in raw_decode
  raise JSONDecodeError("Expecting value", s, err.value) from None

json.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)

*** From 'decoder.py' ***

  def raw_decode(self, s, idx=0):
       """Decode a JSON document from ``s`` (a ``str`` beginning with
       a JSON document) and return a 2-tuple of the Python
       representation and the index in ``s`` where the document ended.

       This can be used to decode a JSON document from a string that may
       have extraneous data at the end.

       """
       try:
           obj, end = self.scan_once(s, idx)
       except StopIteration as err:
           raise JSONDecodeError("Expecting value", s, err.value) from None
       return obj, end

Issue with line 355: raise JSONDecodeError("Expecting value", s, err.value) from None

Make save yaml optional

I think it could be useful to add an option to not necessarily write out the token yaml every time when initiating.

Seems to be a problem with the link to refresh token

Maybe a url link needs to be updated in your functions. If I can update it myself I don't mind doing it, if you can tell me where it is in the code.¸

I simply coded:

from qtrade import Questrade
qtrade = Questrade(access_code='abc') #abc is my personnel token

The output is the error code:

raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 500 Server Error: Internal Server Error for url: https://login.questrade.com/oauth2/token?grant_type=refresh_token&refresh_token=MyqKC7-5ywmcBAPmPzjJfzjgToQYM3P90

Your help would be apprciated

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.