103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
|
"""Nordnet Depository fetching for Nordnet, the scandinavian investment bank.
|
||
|
|
||
|
This fetcher uses the [Nordnet
|
||
|
API](https://www.nordnet.dk/externalapi/docs/api), which requires a Nordnet
|
||
|
account.
|
||
|
|
||
|
Some of the code here is based on [Morten Helmstedt's Nordnet
|
||
|
utilities](https://github.com/helmstedt/nordnet-utilities/blob/main/nordnet_login.py).
|
||
|
I am grateful for his pioneering work.
|
||
|
"""
|
||
|
|
||
|
import datetime
|
||
|
import logging
|
||
|
from decimal import Decimal
|
||
|
|
||
|
import fin_defs
|
||
|
|
||
|
from .data import Depo, DepoSingle
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
|
||
|
API_HOSTNAME = 'public.nordnet.dk'
|
||
|
API_ROOT = f'https://{API_HOSTNAME}/api/2'
|
||
|
|
||
|
API_LOGIN_STEP_1 = 'https://www.nordnet.dk/logind'
|
||
|
API_LOGIN_STEP_2 = API_ROOT + '/authentication/basic/login'
|
||
|
|
||
|
API_ACCOUNTS = API_ROOT + '/accounts'
|
||
|
API_ACCOUNT_INFO = API_ROOT + '/accounts/{accid}/info'
|
||
|
API_ACCOUNT_LEDGERS = API_ROOT + '/accounts/{accid}/ledgers'
|
||
|
API_ACCOUNT_POSITIONS = API_ROOT + '/accounts/{accid}/positions'
|
||
|
|
||
|
|
||
|
class NordnetDepoFetcher:
|
||
|
def __init__(self, session, username: str, password: str):
|
||
|
assert session is not None, 'missing session'
|
||
|
self.session = session
|
||
|
self.username = username
|
||
|
self.password = password
|
||
|
self.is_logged_in = False
|
||
|
|
||
|
def get_json(self, url: str, params: dict[str, str | int] = {}) -> object:
|
||
|
if not url.startswith(API_ROOT):
|
||
|
msg = f'Given url must be located below API ROOT: {url}'
|
||
|
raise ValueError(msg)
|
||
|
|
||
|
headers = {
|
||
|
'Accepts': 'application/json',
|
||
|
}
|
||
|
response = self.session.get(url, params=params, headers=headers)
|
||
|
|
||
|
json = response.json()
|
||
|
response.raise_for_status()
|
||
|
return json
|
||
|
|
||
|
def login(self):
|
||
|
"""Performs authentication with the login server if not already logged
|
||
|
in. Does not need to be manually called; most methods that require this
|
||
|
information will do it themselves.
|
||
|
|
||
|
This method is based on Morten Helmstedt's version:
|
||
|
<https://github.com/helmstedt/nordnet-utilities/blob/main/nordnet_login.py>
|
||
|
"""
|
||
|
if self.is_logged_in:
|
||
|
return
|
||
|
|
||
|
self.session.get(API_LOGIN_STEP_1)
|
||
|
self.session.headers['client-id'] = 'NEXT'
|
||
|
self.session.headers['sub-client-id'] = 'NEXT'
|
||
|
response = self.session.post(
|
||
|
API_LOGIN_STEP_2,
|
||
|
data={'username': self.username, 'password': self.password},
|
||
|
)
|
||
|
response.raise_for_status()
|
||
|
|
||
|
self.is_logged_in = True
|
||
|
|
||
|
def get_depo(self) -> Depo:
|
||
|
self.login()
|
||
|
|
||
|
accounts = self.get_json(API_ACCOUNTS)
|
||
|
print(accounts)
|
||
|
|
||
|
|
||
|
|
||
|
# Old
|
||
|
now = datetime.datetime.now(tz=datetime.UTC)
|
||
|
result = self.session.query_private('Balance')
|
||
|
assets: dict[fin_defs.Asset, Decimal] = {}
|
||
|
for account, balance_str in result['result'].items():
|
||
|
account = account.removesuffix('.HOLD')
|
||
|
asset = fin_defs.WELL_KNOWN_SYMBOLS[account]
|
||
|
balance = Decimal(balance_str)
|
||
|
if balance != 0:
|
||
|
assets[asset] = assets.get(asset, Decimal(0)) + balance
|
||
|
del account, balance_str, asset, balance
|
||
|
|
||
|
return DepoSingle(
|
||
|
name='nordnet',
|
||
|
_assets=assets,
|
||
|
updated_time=now,
|
||
|
)
|