Compare commits
4 Commits
4e8214a948
...
7eedfa2100
Author | SHA1 | Date | |
---|---|---|---|
7eedfa2100 | |||
e993acf3af | |||
ef417c6175 | |||
88a57bbc6c |
|
@ -3,6 +3,7 @@ import dataclasses
|
||||||
import datetime
|
import datetime
|
||||||
from collections.abc import Iterable, Mapping
|
from collections.abc import Iterable, Mapping
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
import enforce_typing
|
import enforce_typing
|
||||||
from fin_defs import Asset
|
from fin_defs import Asset
|
||||||
|
@ -69,3 +70,25 @@ class DepoGroup(Depo):
|
||||||
summed += nested_depo.get_amount_of_asset(asset)
|
summed += nested_depo.get_amount_of_asset(asset)
|
||||||
del nested_depo
|
del nested_depo
|
||||||
return summed
|
return summed
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
class DepoFetcher(abc.ABC):
|
||||||
|
"""Base `Depo` fetcher interface.
|
||||||
|
|
||||||
|
Used to get depository information for specific websites/backends.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_depo(self) -> Depo:
|
||||||
|
"""Fetches the `Depo`s available for the fetcher, possibly as
|
||||||
|
a `DepoGroup`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def assert_param(self, param_name: str, param_type: type[T], param_value: T) -> T:
|
||||||
|
if not isinstance(param_value, param_type):
|
||||||
|
msg = f'{self} expected {param_name} parameter of type {param_type}, but got: {param_value}'
|
||||||
|
raise TypeError(msg)
|
||||||
|
return param_value
|
||||||
|
|
|
@ -7,12 +7,12 @@ from decimal import Decimal
|
||||||
import fin_defs
|
import fin_defs
|
||||||
import krakenex
|
import krakenex
|
||||||
|
|
||||||
from .data import Depo, DepoSingle
|
from .data import Depo, DepoFetcher, DepoSingle
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class KrakenDepoFetcher:
|
class KrakenDepoFetcher(DepoFetcher):
|
||||||
"""Depository fetcher for [Kraken](https://www.kraken.com), the online crypto currency exchange.
|
"""Depository fetcher for [Kraken](https://www.kraken.com), the online crypto currency exchange.
|
||||||
|
|
||||||
Requirements for use:
|
Requirements for use:
|
||||||
|
@ -22,13 +22,14 @@ class KrakenDepoFetcher:
|
||||||
settings](https://pro.kraken.com/app/settings/api).
|
settings](https://pro.kraken.com/app/settings/api).
|
||||||
API key must have the **Query Funds Permission**, and **should not have
|
API key must have the **Query Funds Permission**, and **should not have
|
||||||
any additional permissions**. Employ principle of least priviledge.
|
any additional permissions**. Employ principle of least priviledge.
|
||||||
|
- Install [`krakenex`](https://pypi.org/project/krakenex/) library.
|
||||||
|
|
||||||
Depository structure: A `DepoSingle`. No nesting.
|
Depository structure: A `DepoSingle`. No nesting.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, kraken_key: str, kraken_secret: str):
|
def __init__(self, kraken_key: str, kraken_secret: str):
|
||||||
assert kraken_key is not None, 'Missing kraken_key'
|
self.assert_param('kraken_key', str, kraken_key)
|
||||||
assert kraken_secret is not None, 'Missing kraken_secret'
|
self.assert_param('kraken_secret', str, kraken_secret)
|
||||||
self.client = krakenex.API(
|
self.client = krakenex.API(
|
||||||
kraken_key,
|
kraken_key,
|
||||||
kraken_secret,
|
kraken_secret,
|
||||||
|
@ -40,13 +41,13 @@ class KrakenDepoFetcher:
|
||||||
result = self.client.query_private('Balance')
|
result = self.client.query_private('Balance')
|
||||||
|
|
||||||
assets: dict[fin_defs.Asset, Decimal] = {}
|
assets: dict[fin_defs.Asset, Decimal] = {}
|
||||||
for account, balance_str in result['result'].items():
|
for account_raw, balance_str in result['result'].items():
|
||||||
account = account.removesuffix('.HOLD')
|
account = account_raw.removesuffix('.HOLD')
|
||||||
asset = fin_defs.WELL_KNOWN_SYMBOLS[account]
|
asset = fin_defs.WELL_KNOWN_SYMBOLS[account]
|
||||||
balance = Decimal(balance_str)
|
balance = Decimal(balance_str)
|
||||||
if balance != 0:
|
if balance != 0:
|
||||||
assets[asset] = assets.get(asset, Decimal(0)) + balance
|
assets[asset] = assets.get(asset, Decimal(0)) + balance
|
||||||
del account, balance_str, asset, balance
|
del account, account_raw, balance_str, asset, balance
|
||||||
|
|
||||||
return DepoSingle(
|
return DepoSingle(
|
||||||
name='Kraken',
|
name='Kraken',
|
||||||
|
|
|
@ -7,13 +7,13 @@ from decimal import Decimal
|
||||||
import fin_defs
|
import fin_defs
|
||||||
import kucoin.client
|
import kucoin.client
|
||||||
|
|
||||||
from .data import DepoGroup, DepoSingle
|
from .data import DepoFetcher, DepoGroup, DepoSingle
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class KucoinDepoFetcher:
|
class KucoinDepoFetcher(DepoFetcher):
|
||||||
"""Depository fetcher for [Kucoin](https://www.kucoin.com), the online crypto currency exchange.
|
"""`Depo` fetcher for [Kucoin](https://www.kucoin.com), the online crypto currency exchange.
|
||||||
|
|
||||||
Requirements for use:
|
Requirements for use:
|
||||||
- Account on [Kucoin](https://www.kucoin.com).
|
- Account on [Kucoin](https://www.kucoin.com).
|
||||||
|
@ -21,15 +21,16 @@ class KucoinDepoFetcher:
|
||||||
- Created API key from [settings menu](https://www.kucoin.com/account/api).
|
- Created API key from [settings menu](https://www.kucoin.com/account/api).
|
||||||
API key must have the **General Permission**, and **should not have
|
API key must have the **General Permission**, and **should not have
|
||||||
any additional permissions**. Employ principle of least priviledge.
|
any additional permissions**. Employ principle of least priviledge.
|
||||||
|
- Install [`python-kucoin`](https://python-kucoin.readthedocs.io/en/latest/) library.
|
||||||
|
|
||||||
Depository structure: An upper level depo split for each of the
|
Depository structure: An upper level depo split for each of the
|
||||||
sub-accounts (Funding, Trading, Margin, Futures...)
|
sub-accounts (Funding, Trading, Margin, Futures...)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, kucoin_key: str, kucoin_secret: str, kucoin_pass: str):
|
def __init__(self, kucoin_key: str, kucoin_secret: str, kucoin_pass: str):
|
||||||
assert kucoin_key is not None, 'Missing kucoin_key'
|
self.assert_param('kucoin_key', str, kucoin_key)
|
||||||
assert kucoin_secret is not None, 'Missing kucoin_secret'
|
self.assert_param('kucoin_secret', str, kucoin_secret)
|
||||||
assert kucoin_pass is not None, 'Missing kucoin_pass'
|
self.assert_param('kucoin_pass', str, kucoin_pass)
|
||||||
self.client = kucoin.client.Client(
|
self.client = kucoin.client.Client(
|
||||||
kucoin_key,
|
kucoin_key,
|
||||||
kucoin_secret,
|
kucoin_secret,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import fin_defs
|
||||||
import requests
|
import requests
|
||||||
from frozendict import frozendict
|
from frozendict import frozendict
|
||||||
|
|
||||||
from .data import DepoGroup, DepoSingle
|
from .data import DepoFetcher, DepoGroup, DepoSingle
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ BYOC_ASSETS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PartisiaBlockchainAccountDepoFetcher:
|
class PartisiaBlockchainAccountDepoFetcher(DepoFetcher):
|
||||||
"""Depository fetcher for individual [Partisia
|
"""Depository fetcher for individual [Partisia
|
||||||
Blockchain](https://partisiablockchain.com/) accounts, including [MPC](https://partisiablockchain.gitlab.io/documentation/pbc-fundamentals/mpc-token-model-and-account-elements.html) and
|
Blockchain](https://partisiablockchain.com/) accounts, including [MPC](https://partisiablockchain.gitlab.io/documentation/pbc-fundamentals/mpc-token-model-and-account-elements.html) and
|
||||||
[Bring-Your-Own-Coin](https://partisiablockchain.gitlab.io/documentation/pbc-fundamentals/byoc/introduction-to-byoc.html).
|
[Bring-Your-Own-Coin](https://partisiablockchain.gitlab.io/documentation/pbc-fundamentals/byoc/introduction-to-byoc.html).
|
||||||
|
@ -150,9 +150,9 @@ class PartisiaBlockchainAccountDepoFetcher:
|
||||||
depo.
|
depo.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session, blockchain_address: str):
|
def __init__(self, session: requests.Session, blockchain_address: str):
|
||||||
assert session is not None, 'missing session'
|
self.assert_param('session', requests.Session, session)
|
||||||
assert blockchain_address is not None, 'missing blockchain_address'
|
self.assert_param('blockchain_address', str, blockchain_address)
|
||||||
|
|
||||||
self.client = PbcClient(session)
|
self.client = PbcClient(session)
|
||||||
self.blockchain_address = blockchain_address
|
self.blockchain_address = blockchain_address
|
||||||
|
@ -166,7 +166,7 @@ class PartisiaBlockchainAccountDepoFetcher:
|
||||||
{fin_defs.MPC: balances.mpc},
|
{fin_defs.MPC: balances.mpc},
|
||||||
)
|
)
|
||||||
depo_byoc = DepoSingle(
|
depo_byoc = DepoSingle(
|
||||||
'BYOC',
|
'Bring Your Own Coin',
|
||||||
balances.update_time,
|
balances.update_time,
|
||||||
{BYOC_ASSETS[k]: v for k, v in balances.byoc.items()},
|
{BYOC_ASSETS[k]: v for k, v in balances.byoc.items()},
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,9 +14,10 @@ import logging
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
import fin_defs
|
import fin_defs
|
||||||
|
import requests
|
||||||
from frozendict import frozendict
|
from frozendict import frozendict
|
||||||
|
|
||||||
from .data import Depo, DepoGroup, DepoSingle
|
from .data import Depo, DepoFetcher, DepoGroup, DepoSingle
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -39,9 +40,11 @@ def asset_from_instrument_json(json) -> fin_defs.Asset | None:
|
||||||
exchange_id = json['tradables'][0]['mic']
|
exchange_id = json['tradables'][0]['mic']
|
||||||
return fin_defs.Stock(symbol, fin_defs.EXCHANGES_BY_IDS[exchange_id])
|
return fin_defs.Stock(symbol, fin_defs.EXCHANGES_BY_IDS[exchange_id])
|
||||||
|
|
||||||
EMPTY_DICT: dict[str,str | int] = frozendict()
|
|
||||||
|
|
||||||
class NordnetDepoFetcher:
|
EMPTY_DICT: dict[str, str | int] = frozendict()
|
||||||
|
|
||||||
|
|
||||||
|
class NordnetDepoFetcher(DepoFetcher):
|
||||||
"""Depository fetcher for [Nordnet](https://nordnet.dk), the online investment bank.
|
"""Depository fetcher for [Nordnet](https://nordnet.dk), the online investment bank.
|
||||||
|
|
||||||
Requirements for use:
|
Requirements for use:
|
||||||
|
@ -57,23 +60,13 @@ class NordnetDepoFetcher:
|
||||||
`Depo`, with all of them nested under a "Nordnet" nested depository.
|
`Depo`, with all of them nested under a "Nordnet" nested depository.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session, username: str, password: str):
|
def __init__(self, session: requests.Session, username: str, password: str):
|
||||||
self.session = session
|
self.session = self.assert_param('session', requests.Session, session)
|
||||||
self.username = username
|
self.username: str = self.assert_param('username', str, username)
|
||||||
self.password = password
|
self.password: str = self.assert_param('password', str, password)
|
||||||
self.is_logged_in = False
|
self.is_logged_in = False
|
||||||
|
|
||||||
if self.session is None:
|
def _get_json(self, url: str, params: dict[str, str | int] = EMPTY_DICT) -> dict:
|
||||||
msg = 'Session argument is missing'
|
|
||||||
raise TypeError(msg)
|
|
||||||
if self.username is not None:
|
|
||||||
msg = 'Username argument s missing'
|
|
||||||
raise TypeError(msg)
|
|
||||||
if self.password is not None:
|
|
||||||
msg = 'Password argument is missing'
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
def get_json(self, url: str, params: dict[str, str | int] = EMPTY_DICT)-> dict:
|
|
||||||
if not url.startswith(API_ROOT):
|
if not url.startswith(API_ROOT):
|
||||||
msg = f'Given url must be located below API ROOT: {url}'
|
msg = f'Given url must be located below API ROOT: {url}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
@ -115,7 +108,7 @@ class NordnetDepoFetcher:
|
||||||
tz=datetime.UTC,
|
tz=datetime.UTC,
|
||||||
) # TODO: Use info from json requests
|
) # TODO: Use info from json requests
|
||||||
|
|
||||||
json_accounts = self.get_json(API_ACCOUNTS)
|
json_accounts = self._get_json(API_ACCOUNTS)
|
||||||
|
|
||||||
nested: list[Depo] = []
|
nested: list[Depo] = []
|
||||||
for json_account in json_accounts:
|
for json_account in json_accounts:
|
||||||
|
@ -125,7 +118,7 @@ class NordnetDepoFetcher:
|
||||||
|
|
||||||
# Determine amount of usable currency stored on Nordnet
|
# Determine amount of usable currency stored on Nordnet
|
||||||
if True:
|
if True:
|
||||||
json_account_info = self.get_json(
|
json_account_info = self._get_json(
|
||||||
API_ACCOUNT_INFO.format(accid=account_id),
|
API_ACCOUNT_INFO.format(accid=account_id),
|
||||||
)
|
)
|
||||||
asset = fin_defs.FiatCurrency(
|
asset = fin_defs.FiatCurrency(
|
||||||
|
@ -136,7 +129,7 @@ class NordnetDepoFetcher:
|
||||||
del asset, amount
|
del asset, amount
|
||||||
|
|
||||||
# Determine positions on Nordnet
|
# Determine positions on Nordnet
|
||||||
json_account_positions = self.get_json(
|
json_account_positions = self._get_json(
|
||||||
API_ACCOUNT_POSITIONS.format(accid=account_id),
|
API_ACCOUNT_POSITIONS.format(accid=account_id),
|
||||||
)
|
)
|
||||||
for pos in json_account_positions:
|
for pos in json_account_positions:
|
||||||
|
|
|
@ -14,11 +14,11 @@ needs_secrets = pytest.mark.skipif(
|
||||||
@needs_secrets
|
@needs_secrets
|
||||||
def test_get_depo():
|
def test_get_depo():
|
||||||
session = requests.Session()
|
session = requests.Session()
|
||||||
nordnet = investbank_nordnet.NordnetDepoFetcher(
|
fetcher = investbank_nordnet.NordnetDepoFetcher(
|
||||||
session,
|
session,
|
||||||
secrets.NORDNET_USERNAME,
|
secrets.NORDNET_USERNAME,
|
||||||
secrets.NORDNET_PASSWORD,
|
secrets.NORDNET_PASSWORD,
|
||||||
)
|
)
|
||||||
|
|
||||||
depo = nordnet.get_depo()
|
depo = fetcher.get_depo()
|
||||||
assert depo is not None
|
assert depo is not None
|
||||||
|
|
23
test/test_partisia_blockchain.py
Normal file
23
test/test_partisia_blockchain.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import fin_defs
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from fin_depo import defi_partisia_blockchain
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_depo():
|
||||||
|
session = requests.Session()
|
||||||
|
fetcher = defi_partisia_blockchain.PartisiaBlockchainAccountDepoFetcher(
|
||||||
|
session,
|
||||||
|
'00f3a778fbcf663e4f0d47b41aa4ec51e0f9d39d60',
|
||||||
|
)
|
||||||
|
|
||||||
|
depo = fetcher.get_depo()
|
||||||
|
assert depo is not None
|
||||||
|
assert depo.name == 'Partisia Blockchain'
|
||||||
|
assert depo.updated_time is not None
|
||||||
|
assert depo.nested[0].name == 'Native'
|
||||||
|
assert depo.nested[0].updated_time == depo.updated_time
|
||||||
|
assert depo.nested[1].name == 'Bring Your Own Coin'
|
||||||
|
assert depo.nested[1].updated_time == depo.updated_time
|
||||||
|
|
||||||
|
assert depo.get_amount_of_asset(fin_defs.MPC)
|
Loading…
Reference in New Issue
Block a user