Add basic practice account support
parent
435b2a56e8
commit
462c419970
|
@ -25,7 +25,8 @@ asks.init('trio')
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
_refresh_token_ep = 'https://login.questrade.com/oauth2/'
|
_use_practice_account = False
|
||||||
|
_refresh_token_ep = 'https://{}login.questrade.com/oauth2/'
|
||||||
_version = 'v1'
|
_version = 'v1'
|
||||||
|
|
||||||
# stock queries/sec
|
# stock queries/sec
|
||||||
|
@ -102,7 +103,10 @@ class _API:
|
||||||
resp = await self._sess.get(path=f'/{path}', params=params)
|
resp = await self._sess.get(path=f'/{path}', params=params)
|
||||||
return resproc(resp, log)
|
return resproc(resp, log)
|
||||||
|
|
||||||
async def _new_auth_token(self, refresh_token: str) -> dict:
|
async def _new_auth_token(
|
||||||
|
self,
|
||||||
|
refresh_token: str,
|
||||||
|
) -> dict:
|
||||||
"""Request a new api authorization ``refresh_token``.
|
"""Request a new api authorization ``refresh_token``.
|
||||||
|
|
||||||
Gain api access using either a user provided or existing token.
|
Gain api access using either a user provided or existing token.
|
||||||
|
@ -112,18 +116,42 @@ class _API:
|
||||||
http://www.questrade.com/api/documentation/security
|
http://www.questrade.com/api/documentation/security
|
||||||
"""
|
"""
|
||||||
resp = await self._sess.get(
|
resp = await self._sess.get(
|
||||||
_refresh_token_ep + 'token',
|
self.client._auth_ep + 'token',
|
||||||
params={'grant_type': 'refresh_token',
|
params={'grant_type': 'refresh_token',
|
||||||
'refresh_token': refresh_token}
|
'refresh_token': refresh_token}
|
||||||
)
|
)
|
||||||
return resproc(resp, log)
|
return resproc(resp, log)
|
||||||
|
|
||||||
|
async def _revoke_auth_token(
|
||||||
|
self,
|
||||||
|
practise: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""Revoke api access for the current token.
|
||||||
|
"""
|
||||||
|
token = self.access_data['refresh_token']
|
||||||
|
log.debug(f"Revoking token {token}")
|
||||||
|
resp = await asks.post(
|
||||||
|
self.client._auth_ep + 'revoke',
|
||||||
|
headers={'token': token}
|
||||||
|
)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
# accounts end points
|
||||||
|
|
||||||
async def accounts(self) -> dict:
|
async def accounts(self) -> dict:
|
||||||
return await self._get('accounts')
|
return await self._get('accounts')
|
||||||
|
|
||||||
async def time(self) -> dict:
|
async def time(self) -> dict:
|
||||||
return await self._get('time')
|
return await self._get('time')
|
||||||
|
|
||||||
|
async def balances(self, id: str) -> dict:
|
||||||
|
return await self._get(f'accounts/{id}/balances')
|
||||||
|
|
||||||
|
async def postions(self, id: str) -> dict:
|
||||||
|
return await self._get(f'accounts/{id}/positions')
|
||||||
|
|
||||||
|
# market end points
|
||||||
|
|
||||||
async def markets(self) -> dict:
|
async def markets(self) -> dict:
|
||||||
return await self._get('markets')
|
return await self._get('markets')
|
||||||
|
|
||||||
|
@ -146,12 +174,6 @@ class _API:
|
||||||
async def candles(self, id: str, start: str, end, interval) -> dict:
|
async def candles(self, id: str, start: str, end, interval) -> dict:
|
||||||
return await self._get(f'markets/candles/{id}', params={})
|
return await self._get(f'markets/candles/{id}', params={})
|
||||||
|
|
||||||
async def balances(self, id: str) -> dict:
|
|
||||||
return await self._get(f'accounts/{id}/balances')
|
|
||||||
|
|
||||||
async def postions(self, id: str) -> dict:
|
|
||||||
return await self._get(f'accounts/{id}/positions')
|
|
||||||
|
|
||||||
async def option_contracts(self, symbol_id: str) -> dict:
|
async def option_contracts(self, symbol_id: str) -> dict:
|
||||||
"Retrieve all option contract API ids with expiry -> strike prices."
|
"Retrieve all option contract API ids with expiry -> strike prices."
|
||||||
contracts = await self._get(f'symbols/{symbol_id}/options')
|
contracts = await self._get(f'symbols/{symbol_id}/options')
|
||||||
|
@ -189,10 +211,16 @@ class Client:
|
||||||
|
|
||||||
Provides a high-level api which wraps the underlying endpoint calls.
|
Provides a high-level api which wraps the underlying endpoint calls.
|
||||||
"""
|
"""
|
||||||
def __init__(self, config: configparser.ConfigParser):
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: configparser.ConfigParser,
|
||||||
|
):
|
||||||
self._sess = asks.Session()
|
self._sess = asks.Session()
|
||||||
self.api = _API(self)
|
self.api = _API(self)
|
||||||
self._conf = config
|
self._conf = config
|
||||||
|
self._is_practise_account = _use_practice_account
|
||||||
|
self._auth_ep = _refresh_token_ep.format(
|
||||||
|
'practice' if _use_practice_account else '')
|
||||||
self.access_data = {}
|
self.access_data = {}
|
||||||
self._reload_config(config)
|
self._reload_config(config)
|
||||||
self._symbol_cache: Dict[str, int] = {}
|
self._symbol_cache: Dict[str, int] = {}
|
||||||
|
@ -209,17 +237,6 @@ class Client:
|
||||||
self._conf = config or get_config(**kwargs)
|
self._conf = config or get_config(**kwargs)
|
||||||
self.access_data = dict(self._conf['questrade'])
|
self.access_data = dict(self._conf['questrade'])
|
||||||
|
|
||||||
async def _revoke_auth_token(self) -> None:
|
|
||||||
"""Revoke api access for the current token.
|
|
||||||
"""
|
|
||||||
token = self.access_data['refresh_token']
|
|
||||||
log.debug(f"Revoking token {token}")
|
|
||||||
resp = await asks.post(
|
|
||||||
_refresh_token_ep + 'revoke',
|
|
||||||
headers={'token': token}
|
|
||||||
)
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def write_config(self):
|
def write_config(self):
|
||||||
"""Save access creds to config file.
|
"""Save access creds to config file.
|
||||||
"""
|
"""
|
||||||
|
@ -256,7 +273,7 @@ class Client:
|
||||||
if not access_token or (
|
if not access_token or (
|
||||||
expires < time.time()
|
expires < time.time()
|
||||||
) or force_refresh:
|
) or force_refresh:
|
||||||
log.info("REFRESHING TOKENS!")
|
log.info("Refreshing API tokens")
|
||||||
log.debug(
|
log.debug(
|
||||||
f"Refreshing access token {access_token} which expired"
|
f"Refreshing access token {access_token} which expired"
|
||||||
f" at {expires_stamp}")
|
f" at {expires_stamp}")
|
||||||
|
@ -304,7 +321,7 @@ class Client:
|
||||||
# write to config to disk
|
# write to config to disk
|
||||||
self.write_config()
|
self.write_config()
|
||||||
else:
|
else:
|
||||||
log.info(
|
log.debug(
|
||||||
f"\nCurrent access token {access_token} expires at"
|
f"\nCurrent access token {access_token} expires at"
|
||||||
f" {expires_stamp}\n")
|
f" {expires_stamp}\n")
|
||||||
|
|
||||||
|
@ -525,17 +542,21 @@ def get_config(
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def get_client() -> Client:
|
async def get_client(**kwargs) -> Client:
|
||||||
"""Spawn a broker client for making requests to the API service.
|
"""Spawn a broker client for making requests to the API service.
|
||||||
"""
|
"""
|
||||||
conf = get_config()
|
conf = get_config(config_path=kwargs.get('config_path'))
|
||||||
log.debug(f"Loaded config:\n{colorize_json(dict(conf['questrade']))}")
|
log.debug(f"Loaded config:\n{colorize_json(dict(conf['questrade']))}")
|
||||||
client = Client(conf)
|
client = Client(conf, **kwargs)
|
||||||
await client.ensure_access()
|
await client.ensure_access()
|
||||||
try:
|
try:
|
||||||
log.debug("Check time to ensure access token is valid")
|
log.debug("Check time to ensure access token is valid")
|
||||||
|
# XXX: the `time()` end point requires acc_read Oauth access.
|
||||||
|
# In order to use a client you need at least one key with this
|
||||||
|
# access enabled in order to do symbol searches and id lookups.
|
||||||
await client.api.time()
|
await client.api.time()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
raise
|
||||||
# access token is likely no good
|
# access token is likely no good
|
||||||
log.warn(f"Access tokens {client.access_data} seem"
|
log.warn(f"Access tokens {client.access_data} seem"
|
||||||
f" expired, forcing refresh")
|
f" expired, forcing refresh")
|
||||||
|
|
Loading…
Reference in New Issue