Skip to content

Commit

Permalink
feat: add kyc_results into broker account (#467)
Browse files Browse the repository at this point in the history
  • Loading branch information
hiohiohio authored Jun 12, 2024
1 parent 46255ae commit f2362cc
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 9 deletions.
39 changes: 32 additions & 7 deletions alpaca/broker/models/accounts.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from datetime import datetime
from typing import List, Optional
from typing import Any, Dict, List, Optional
from uuid import UUID

from pydantic import TypeAdapter, model_validator, field_validator, ValidationInfo
from pydantic import TypeAdapter, ValidationInfo, field_validator, model_validator

from alpaca.broker.models.documents import AccountDocument
from alpaca.broker.enums import (
AgreementType,
ClearingBroker,
Expand All @@ -13,14 +12,33 @@
TaxIdType,
VisaType,
)
from alpaca.broker.models.documents import AccountDocument
from alpaca.common.models import ModelWithID
from alpaca.common.models import ValidateBaseModel as BaseModel
from alpaca.trading.enums import AccountStatus
from alpaca.common.models import (
ModelWithID,
ValidateBaseModel as BaseModel,
)
from alpaca.trading.models import TradeAccount as BaseTradeAccount


class KycResults(BaseModel):
"""
Hold information about the result of KYC.
ref. https://docs.alpaca.markets/reference/getaccount
Attributes:
reject (Optional[Dict[str, Any]]): The reason for the rejection
accept (Optional[Dict[str, Any]]): The reason for the acceptance
indeterminate (Optional[Dict[str, Any]]): The reason for the indeterminate result
additional_information (Optional[str]): Used to display a custom message
summary (Optional[str]): Either pass or fail. Used to indicate if KYC has completed and passed or not.
"""

reject: Optional[Dict[str, Any]] = None
accept: Optional[Dict[str, Any]] = None
indeterminate: Optional[Dict[str, Any]] = None
additional_information: Optional[str] = None
summary: Optional[str] = None


class Contact(BaseModel):
"""User contact details within Account Model
Expand Down Expand Up @@ -218,6 +236,7 @@ class Account(ModelWithID):
account_number (str): A more human friendly identifier for this account
status (AccountStatus): The approval status of this account
crypto_status (Optional[AccountStatus]): The crypto trading status. Only present if crypto trading is enabled.
kyc_results (Optional[KycResult]): Hold information about the result of KYC.
currency (str): The currency the account's values are returned in
last_equity (str): The total equity value stored in the account
created_at (str): The timestamp when the account was created
Expand All @@ -232,6 +251,7 @@ class Account(ModelWithID):
account_number: str
status: AccountStatus
crypto_status: Optional[AccountStatus] = None
kyc_results: Optional[KycResults] = None
currency: str
last_equity: str
created_at: str
Expand All @@ -250,6 +270,11 @@ def __init__(self, **response):
crypto_status=(
response["crypto_status"] if "crypto_status" in response else None
),
kyc_results=(
TypeAdapter(KycResults).validate_python(response["kyc_results"])
if "kyc_results" in response and response["kyc_results"] is not None
else None
),
currency=(response["currency"]),
last_equity=(response["last_equity"]),
created_at=(response["created_at"]),
Expand Down
42 changes: 40 additions & 2 deletions tests/broker/broker_client/test_accounts_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def test_create_account(reqmock, client: BrokerClient):
assert reqmock.called_once
assert type(returned_account) == Account
assert returned_account.id == UUID(created_id)
assert returned_account.kyc_results is None


def test_create_lct_account(reqmock, client: BrokerClient):
Expand Down Expand Up @@ -209,7 +210,8 @@ def test_create_lct_account(reqmock, client: BrokerClient):
},
"account_type": "trading",
"trading_configurations": null,
"currency": "EUR"
"currency": "EUR",
"kyc_results": null
}
""",
)
Expand All @@ -232,6 +234,7 @@ def test_create_lct_account(reqmock, client: BrokerClient):
assert type(returned_account) == Account
assert returned_account.id == UUID(created_id)
assert returned_account.currency == currency
assert returned_account.kyc_results is None


def test_get_account(reqmock, client: BrokerClient):
Expand Down Expand Up @@ -316,7 +319,14 @@ def test_get_account(reqmock, client: BrokerClient):
"email_address": "[email protected]"
},
"account_type": "trading",
"trading_configurations": null
"trading_configurations": null,
"kyc_results": {
"reject": {"IDENTITY_VERIFICATION": {}},
"accept": {"IDENTITY_VERIFICATION": {}},
"indeterminate": {"IDENTITY_VERIFICATION": {}},
"additional_information": "additional_information_test",
"summary": "pass"
}
}
""",
)
Expand All @@ -327,6 +337,13 @@ def test_get_account(reqmock, client: BrokerClient):
assert type(account) == Account
assert account.id == UUID(account_id)

assert account.kyc_results is not None
assert account.kyc_results.reject == {"IDENTITY_VERIFICATION": {}}
assert account.kyc_results.accept == {"IDENTITY_VERIFICATION": {}}
assert account.kyc_results.indeterminate == {"IDENTITY_VERIFICATION": {}}
assert account.kyc_results.additional_information == "additional_information_test"
assert account.kyc_results.summary == "pass"


def test_get_account_account_not_found(reqmock, client: BrokerClient):
account_id = "2a87c088-ffb6-472b-a4a3-cd9305c8605c"
Expand Down Expand Up @@ -473,6 +490,13 @@ def test_update_account(reqmock, client: BrokerClient):
assert account.id == UUID(account_id)
assert account.identity.family_name == family_name

assert account.kyc_results is not None
assert account.kyc_results.reject == {}
assert account.kyc_results.accept == {}
assert account.kyc_results.indeterminate == {}
assert account.kyc_results.additional_information is None
assert account.kyc_results.summary == "pass"


def test_update_account_validates_account_id(reqmock, client: BrokerClient):
# dummy update request just to test param parsing
Expand Down Expand Up @@ -604,6 +628,13 @@ def test_list_accounts_no_params(reqmock, client: BrokerClient):
assert account.trusted_contact is None
assert account.agreements is None

assert account.kyc_results is not None
assert account.kyc_results.reject == {}
assert account.kyc_results.accept == {}
assert account.kyc_results.indeterminate == {}
assert account.kyc_results.additional_information is None
assert account.kyc_results.summary == "pass"


def test_list_accounts_parses_entities_if_present(reqmock, client: BrokerClient):
reqmock.get(
Expand Down Expand Up @@ -724,6 +755,13 @@ def test_list_accounts_parses_entities_if_present(reqmock, client: BrokerClient)
assert account.trusted_contact is None
assert account.agreements is None

assert account.kyc_results is not None
assert account.kyc_results.reject == {}
assert account.kyc_results.accept == {}
assert account.kyc_results.indeterminate == {}
assert account.kyc_results.additional_information is None
assert account.kyc_results.summary == "pass"


def test_get_trade_account_by_id(reqmock, client: BrokerClient):
account_id = "5fc0795e-1f16-40cc-aa90-ede67c39d7a9"
Expand Down

0 comments on commit f2362cc

Please sign in to comment.