Skip to content

Commit 2834090

Browse files
authored
feat: add account_type/account_sub_type (#632)
1 parent ef81237 commit 2834090

File tree

4 files changed

+163
-2
lines changed

4 files changed

+163
-2
lines changed

alpaca/broker/enums.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
from enum import Enum
22

33

4+
class AccountSubType(str, Enum):
5+
"""
6+
The sub type of account
7+
IRA Account only
8+
9+
see https://docs.alpaca.markets/reference/createaccount
10+
"""
11+
12+
TRADITIONAL = "traditional"
13+
ROTH = "roth"
14+
15+
16+
class AccountType(str, Enum):
17+
"""
18+
The type of account
19+
20+
see https://docs.alpaca.markets/reference/createaccount
21+
"""
22+
23+
TRADING = "trading"
24+
CUSTODIAL = "custodial"
25+
DONOR_ADVISED = "donor_advised"
26+
IRA = "ira"
27+
HSA = "hsa"
28+
29+
430
class TaxIdType(str, Enum):
531
"""The various country specific tax identification numbers
632

alpaca/broker/models/accounts.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from datetime import datetime
2-
from typing import Any, Dict, List, Optional
2+
from typing import Any, Dict, List, Optional, Union
33
from uuid import UUID
44

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

77
from alpaca.broker.enums import (
8+
AccountSubType,
9+
AccountType,
810
AgreementType,
911
ClearingBroker,
1012
EmploymentStatus,
@@ -234,6 +236,8 @@ class Account(ModelWithID):
234236
Attributes:
235237
id (str): The account uuid used to reference this account
236238
account_number (str): A more human friendly identifier for this account
239+
account_type (Optional[Union[AccountType, str]]): The type of account
240+
account_sub_type (Optional[Union[AccountSubType, str]]): The sub type of account
237241
status (AccountStatus): The approval status of this account
238242
crypto_status (Optional[AccountStatus]): The crypto trading status. Only present if crypto trading is enabled.
239243
kyc_results (Optional[KycResult]): Hold information about the result of KYC.
@@ -249,6 +253,8 @@ class Account(ModelWithID):
249253
"""
250254

251255
account_number: str
256+
account_type: Optional[Union[AccountType, str]] = None
257+
account_sub_type: Optional[Union[AccountSubType, str]] = None
252258
status: AccountStatus
253259
crypto_status: Optional[AccountStatus] = None
254260
kyc_results: Optional[KycResults] = None
@@ -266,6 +272,8 @@ def __init__(self, **response):
266272
super().__init__(
267273
id=(UUID(response["id"])),
268274
account_number=(response["account_number"]),
275+
account_type=response.get("account_type", None),
276+
account_sub_type=response.get("account_sub_type", None),
269277
status=(response["status"]),
270278
crypto_status=(
271279
response["crypto_status"] if "crypto_status" in response else None

alpaca/broker/requests.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from alpaca.broker.enums import (
88
AccountEntities,
9+
AccountSubType,
10+
AccountType,
911
BankAccountType,
1012
CalendarSubType,
1113
DocumentType,
@@ -120,6 +122,8 @@ class CreateAccountRequest(NonEmptyRequest):
120122
"""Class used to format data necessary for making a request to create a brokerage account
121123
122124
Attributes:
125+
account_type (Optional[AccountType]): The type of account
126+
account_sub_type (Optional[AccountSubType]): The sub type of account
123127
contact (Contact): The contact details for the account holder
124128
identity (Identity): The identity details for the account holder
125129
disclosures (Disclosures): The account holder's political disclosures
@@ -128,6 +132,8 @@ class CreateAccountRequest(NonEmptyRequest):
128132
trusted_contact (TrustedContact): The account holder's trusted contact details
129133
"""
130134

135+
account_type: Optional[Union[AccountType, str]] = None
136+
account_sub_type: Optional[Union[AccountSubType, str]] = None
131137
contact: Contact
132138
identity: Identity
133139
disclosures: Disclosures

tests/broker/broker_client/test_accounts_routes.py

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pytest
88

99
from alpaca.broker.client import BrokerClient
10-
from alpaca.broker.enums import AccountEntities
10+
from alpaca.broker.enums import AccountEntities, AccountSubType, AccountType
1111
from alpaca.broker.models import Account, Contact, Identity, TradeAccount
1212
from alpaca.broker.requests import (
1313
CreateAccountRequest,
@@ -123,9 +123,125 @@ def test_create_account(reqmock, client: BrokerClient):
123123
returned_account = client.create_account(create_data)
124124

125125
assert reqmock.called_once
126+
request_body = reqmock.request_history[0].json()
127+
assert "account_type" not in request_body
128+
assert "account_sub_type" not in request_body
126129
assert type(returned_account) == Account
127130
assert returned_account.id == UUID(created_id)
128131
assert returned_account.kyc_results is None
132+
assert returned_account.account_type == AccountType.TRADING
133+
assert returned_account.account_sub_type is None
134+
135+
136+
def test_create_ira_account(reqmock, client: BrokerClient):
137+
created_id = "0d969814-40d6-4b2b-99ac-2e37427f1ad2"
138+
139+
reqmock.post(
140+
"https://broker-api.sandbox.alpaca.markets/v1/accounts",
141+
text="""
142+
{
143+
"id": "0d969814-40d6-4b2b-99ac-2e37427f1ad2",
144+
"account_number": "682389557",
145+
"status": "SUBMITTED",
146+
"crypto_status": "INACTIVE",
147+
"currency": "USD",
148+
"last_equity": "0",
149+
"created_at": "2022-04-12T17:24:31.30283Z",
150+
"contact": {
151+
"email_address": "cool_alpaca@example.com",
152+
"phone_number": "555-666-7788",
153+
"street_address": [
154+
"20 N San Mateo Dr"
155+
],
156+
"unit": "Apt 1A",
157+
"city": "San Mateo",
158+
"state": "CA",
159+
"postal_code": "94401"
160+
},
161+
"identity": {
162+
"given_name": "John",
163+
"family_name": "Doe",
164+
"middle_name": "Smith",
165+
"date_of_birth": "1990-01-01",
166+
"tax_id_type": "USA_SSN",
167+
"country_of_citizenship": "USA",
168+
"country_of_birth": "USA",
169+
"country_of_tax_residence": "USA",
170+
"funding_source": [
171+
"employment_income"
172+
],
173+
"visa_type": null,
174+
"visa_expiration_date": null,
175+
"date_of_departure_from_usa": null,
176+
"permanent_resident": null
177+
},
178+
"disclosures": {
179+
"is_control_person": false,
180+
"is_affiliated_exchange_or_finra": false,
181+
"is_politically_exposed": false,
182+
"immediate_family_exposed": false,
183+
"is_discretionary": false
184+
},
185+
"agreements": [
186+
{
187+
"agreement": "margin_agreement",
188+
"signed_at": "2020-09-11T18:09:33Z",
189+
"ip_address": "185.13.21.99",
190+
"revision": "16.2021.05"
191+
},
192+
{
193+
"agreement": "account_agreement",
194+
"signed_at": "2020-09-11T18:13:44Z",
195+
"ip_address": "185.13.21.99",
196+
"revision": "16.2021.05"
197+
},
198+
{
199+
"agreement": "customer_agreement",
200+
"signed_at": "2020-09-11T18:13:44Z",
201+
"ip_address": "185.13.21.99",
202+
"revision": "16.2021.05"
203+
},
204+
{
205+
"agreement": "crypto_agreement",
206+
"signed_at": "2020-09-11T18:13:44Z",
207+
"ip_address": "185.13.21.99",
208+
"revision": "04.2021.10"
209+
}
210+
],
211+
"trusted_contact": {
212+
"given_name": "Jane",
213+
"family_name": "Doe",
214+
"email_address": "jane.doe@example.com"
215+
},
216+
"account_type": "ira",
217+
"account_sub_type": "traditional",
218+
"trading_configurations": null
219+
}
220+
""",
221+
)
222+
223+
create_data = CreateAccountRequest(
224+
account_type=AccountType.IRA,
225+
account_sub_type=AccountSubType.TRADITIONAL,
226+
agreements=factory.create_dummy_agreements(),
227+
contact=factory.create_dummy_contact(),
228+
disclosures=factory.create_dummy_disclosures(),
229+
documents=factory.create_dummy_account_documents(),
230+
identity=factory.create_dummy_identity(),
231+
trusted_contact=factory.create_dummy_trusted_contact(),
232+
)
233+
234+
returned_account = client.create_account(create_data)
235+
236+
assert reqmock.called_once
237+
request_body = reqmock.request_history[0].json()
238+
assert request_body["account_type"] == "ira"
239+
assert request_body["account_sub_type"] == "traditional"
240+
assert type(returned_account) == Account
241+
assert returned_account.id == UUID(created_id)
242+
assert returned_account.kyc_results is None
243+
assert returned_account.account_type == AccountType.IRA
244+
assert returned_account.account_sub_type == AccountSubType.TRADITIONAL
129245

130246

131247
def test_create_lct_account(reqmock, client: BrokerClient):
@@ -231,10 +347,15 @@ def test_create_lct_account(reqmock, client: BrokerClient):
231347
returned_account = client.create_account(create_data)
232348

233349
assert reqmock.called_once
350+
request_body = reqmock.request_history[0].json()
351+
assert "account_type" not in request_body
352+
assert "account_sub_type" not in request_body
234353
assert type(returned_account) == Account
235354
assert returned_account.id == UUID(created_id)
236355
assert returned_account.currency == currency
237356
assert returned_account.kyc_results is None
357+
assert returned_account.account_type == "trading"
358+
assert returned_account.account_sub_type is None
238359

239360

240361
def test_get_account(reqmock, client: BrokerClient):

0 commit comments

Comments
 (0)