alpaca-py-nopandas 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. alpaca/__init__.py +2 -0
  2. alpaca/broker/__init__.py +8 -0
  3. alpaca/broker/client.py +2360 -0
  4. alpaca/broker/enums.py +528 -0
  5. alpaca/broker/models/__init__.py +7 -0
  6. alpaca/broker/models/accounts.py +347 -0
  7. alpaca/broker/models/cip.py +265 -0
  8. alpaca/broker/models/documents.py +159 -0
  9. alpaca/broker/models/funding.py +114 -0
  10. alpaca/broker/models/journals.py +71 -0
  11. alpaca/broker/models/rebalancing.py +80 -0
  12. alpaca/broker/models/trading.py +13 -0
  13. alpaca/broker/requests.py +1135 -0
  14. alpaca/common/__init__.py +6 -0
  15. alpaca/common/constants.py +13 -0
  16. alpaca/common/enums.py +64 -0
  17. alpaca/common/exceptions.py +47 -0
  18. alpaca/common/models.py +21 -0
  19. alpaca/common/requests.py +82 -0
  20. alpaca/common/rest.py +438 -0
  21. alpaca/common/types.py +7 -0
  22. alpaca/common/utils.py +89 -0
  23. alpaca/data/__init__.py +5 -0
  24. alpaca/data/enums.py +184 -0
  25. alpaca/data/historical/__init__.py +13 -0
  26. alpaca/data/historical/corporate_actions.py +76 -0
  27. alpaca/data/historical/crypto.py +299 -0
  28. alpaca/data/historical/news.py +63 -0
  29. alpaca/data/historical/option.py +230 -0
  30. alpaca/data/historical/screener.py +72 -0
  31. alpaca/data/historical/stock.py +226 -0
  32. alpaca/data/historical/utils.py +30 -0
  33. alpaca/data/live/__init__.py +11 -0
  34. alpaca/data/live/crypto.py +168 -0
  35. alpaca/data/live/news.py +62 -0
  36. alpaca/data/live/option.py +88 -0
  37. alpaca/data/live/stock.py +199 -0
  38. alpaca/data/live/websocket.py +390 -0
  39. alpaca/data/mappings.py +84 -0
  40. alpaca/data/models/__init__.py +7 -0
  41. alpaca/data/models/bars.py +83 -0
  42. alpaca/data/models/base.py +45 -0
  43. alpaca/data/models/corporate_actions.py +309 -0
  44. alpaca/data/models/news.py +90 -0
  45. alpaca/data/models/orderbooks.py +59 -0
  46. alpaca/data/models/quotes.py +78 -0
  47. alpaca/data/models/screener.py +68 -0
  48. alpaca/data/models/snapshots.py +132 -0
  49. alpaca/data/models/trades.py +204 -0
  50. alpaca/data/requests.py +580 -0
  51. alpaca/data/timeframe.py +148 -0
  52. alpaca/py.typed +0 -0
  53. alpaca/trading/__init__.py +5 -0
  54. alpaca/trading/client.py +784 -0
  55. alpaca/trading/enums.py +412 -0
  56. alpaca/trading/models.py +697 -0
  57. alpaca/trading/requests.py +604 -0
  58. alpaca/trading/stream.py +225 -0
  59. alpaca_py_nopandas-0.1.0.dist-info/LICENSE +201 -0
  60. alpaca_py_nopandas-0.1.0.dist-info/METADATA +299 -0
  61. alpaca_py_nopandas-0.1.0.dist-info/RECORD +62 -0
  62. alpaca_py_nopandas-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,1135 @@
1
+ from datetime import date, datetime
2
+ from typing import Any, Dict, List, Optional, Union
3
+ from uuid import UUID
4
+
5
+ from pydantic import field_serializer, field_validator, model_validator
6
+
7
+ from alpaca.broker.enums import (
8
+ AccountEntities,
9
+ BankAccountType,
10
+ CalendarSubType,
11
+ DocumentType,
12
+ DriftBandSubType,
13
+ FeePaymentMethod,
14
+ FundingSource,
15
+ IdentifierType,
16
+ JournalEntryType,
17
+ JournalStatus,
18
+ PortfolioStatus,
19
+ RebalancingConditionsType,
20
+ RunType,
21
+ TradeDocumentType,
22
+ TransferDirection,
23
+ TransferTiming,
24
+ TransferType,
25
+ UploadDocumentMimeType,
26
+ UploadDocumentSubType,
27
+ VisaType,
28
+ WeightType,
29
+ )
30
+ from alpaca.broker.models.accounts import (
31
+ AccountDocument,
32
+ Agreement,
33
+ Contact,
34
+ Disclosures,
35
+ Identity,
36
+ TrustedContact,
37
+ )
38
+ from alpaca.broker.models.documents import W8BenDocument
39
+ from alpaca.common.enums import Sort, SupportedCurrencies
40
+ from alpaca.common.models import BaseModel
41
+ from alpaca.common.requests import NonEmptyRequest
42
+ from alpaca.trading.enums import AccountStatus, ActivityType, AssetClass, OrderType
43
+ from alpaca.trading.requests import LimitOrderRequest as BaseLimitOrderRequest
44
+ from alpaca.trading.requests import MarketOrderRequest as BaseMarketOrderRequest
45
+ from alpaca.trading.requests import OrderRequest as BaseOrderRequest
46
+ from alpaca.trading.requests import StopLimitOrderRequest as BaseStopLimitOrderRequest
47
+ from alpaca.trading.requests import StopOrderRequest as BaseStopOrderRequest
48
+ from alpaca.trading.requests import (
49
+ TrailingStopOrderRequest as BaseTrailingStopOrderRequest,
50
+ )
51
+
52
+ # ############################## Accounts ################################# #
53
+
54
+
55
+ class UploadW8BenDocumentRequest(NonEmptyRequest):
56
+ """
57
+ Attributes:
58
+ content (Optional[str]): A string containing Base64 encoded data to upload. Must be set if `content_data` is not
59
+ set.
60
+ content_data (Optional[W8BenDocument]): The data representing a W8BEN document in field form. Must be set if
61
+ `content` is not set.
62
+ mime_type (UploadDocumentMimeType): The mime type of the data in `content`, or if using `content_data` must be
63
+ UploadDocumentMimeType.JSON. If `content_data` is set this will default to JSON
64
+ """
65
+
66
+ # These 2 are purposely undocumented as they should be here for NonEmptyRequest but they shouldn't be touched or
67
+ # set by users since they always need to be set values
68
+ document_type: DocumentType
69
+ document_sub_type: UploadDocumentSubType
70
+
71
+ content: Optional[str] = None
72
+ content_data: Optional[W8BenDocument] = None
73
+ mime_type: UploadDocumentMimeType
74
+
75
+ def __init__(self, **data) -> None:
76
+ # Always set these to their expected values
77
+ data["document_type"] = DocumentType.W8BEN
78
+ data["document_sub_type"] = UploadDocumentSubType.FORM_W8_BEN
79
+
80
+ if (
81
+ "mime_type" not in data
82
+ and "content_data" in data
83
+ and data["content_data"] is not None
84
+ ):
85
+ data["mime_type"] = UploadDocumentMimeType.JSON
86
+
87
+ super().__init__(**data)
88
+
89
+ @model_validator(mode="before")
90
+ def root_validator(cls, values: dict) -> dict:
91
+ content_is_none = values.get("content", None) is None
92
+ content_data_is_none = values.get("content_data", None) is None
93
+
94
+ if content_is_none and content_data_is_none:
95
+ raise ValueError(
96
+ "You must specify one of either the `content` or `content_data` fields"
97
+ )
98
+
99
+ if not content_is_none and not content_data_is_none:
100
+ raise ValueError(
101
+ "You can only specify one of either the `content` or `content_data` fields"
102
+ )
103
+
104
+ if values["document_type"] != DocumentType.W8BEN:
105
+ raise ValueError("document_type must be W8BEN.")
106
+
107
+ if values["document_sub_type"] != UploadDocumentSubType.FORM_W8_BEN:
108
+ raise ValueError("document_sub_type must be FORM_W8_BEN.")
109
+
110
+ if (
111
+ not content_data_is_none
112
+ and values["mime_type"] != UploadDocumentMimeType.JSON
113
+ ):
114
+ raise ValueError("If `content_data` is set then `mime_type` must be JSON")
115
+
116
+ return values
117
+
118
+
119
+ class CreateAccountRequest(NonEmptyRequest):
120
+ """Class used to format data necessary for making a request to create a brokerage account
121
+
122
+ Attributes:
123
+ contact (Contact): The contact details for the account holder
124
+ identity (Identity): The identity details for the account holder
125
+ disclosures (Disclosures): The account holder's political disclosures
126
+ agreements (List[Agreement]): The agreements the account holder has signed
127
+ documents (List[Union[AccountDocument, UploadW8BenDocumentRequest]]): The documents the account holder has submitted
128
+ trusted_contact (TrustedContact): The account holder's trusted contact details
129
+ """
130
+
131
+ contact: Contact
132
+ identity: Identity
133
+ disclosures: Disclosures
134
+ agreements: List[Agreement]
135
+ documents: Optional[List[Union[AccountDocument, UploadW8BenDocumentRequest]]] = None
136
+ trusted_contact: Optional[TrustedContact] = None
137
+ currency: Optional[SupportedCurrencies] = None # None = USD
138
+ enabled_assets: Optional[List[AssetClass]] = None # None = Default to server
139
+
140
+ @model_validator(mode="before")
141
+ def validate_parameters_only_optional_in_response(cls, values: dict) -> dict:
142
+ """
143
+ Validate parameters that are optional in the response but not in the request.
144
+ """
145
+ nullable_fields_by_model = {
146
+ "contact": "phone_number",
147
+ "identity": "date_of_birth",
148
+ "disclosures": "is_control_person",
149
+ "disclosures": "is_affiliated_exchange_or_finra",
150
+ "disclosures": "is_politically_exposed",
151
+ }
152
+ for model, field in nullable_fields_by_model.items():
153
+ if dict(values[model]).get(field, None) is None:
154
+ raise ValueError(f"{field} is required to create a new account.")
155
+ return values
156
+
157
+
158
+ class UpdatableContact(Contact):
159
+ """
160
+ An extended version of Contact that has all fields as optional, so you don't need to specify all fields if you only
161
+ want to update a subset of them.
162
+
163
+ Attributes:
164
+ email_address (Optional[str]): The user's email address
165
+ phone_number (Optional[str]): The user's phone number. It should include the country code.
166
+ street_address (Optional[List[str]]): The user's street address lines.
167
+ unit (Optional[str]): The user's apartment unit, if any.
168
+ city (Optional[str]): The city the user resides in.
169
+ state (Optional[str]): The state the user resides in. This is required if country is 'USA'.
170
+ postal_code (Optional[str]): The user's postal
171
+ country (Optional[str]): The country the user resides in. 3 letter country code is permissible.
172
+ """
173
+
174
+ # override the non-optional fields to now be optional
175
+ email_address: Optional[str] = None
176
+ phone_number: Optional[str] = None
177
+ street_address: Optional[List[str]] = None
178
+ city: Optional[str] = None
179
+
180
+
181
+ # We don't extend the Identity model because we have to remove fields, not all of them are updatable
182
+ class UpdatableIdentity(NonEmptyRequest):
183
+ """
184
+ This class is a subset version of Identity. Currently, not all fields on accounts are modifiable so this class
185
+ represents which ones are modifiable on the `identity` field of an account when making an
186
+ BrokerClient::update_account call.
187
+
188
+ Also has all fields as optional, so you don't need to specify all fields if you only want to update a subset
189
+
190
+ Attributes:
191
+ given_name (Optional[str]): The user's first name
192
+ middle_name (Optional[str]): The user's middle name, if any
193
+ family_name (Optional[str]): The user's last name
194
+ tax_id (Optional[str]): The user's country specific tax id, required if tax_id_type is provided
195
+ tax_id_type (Optional[TaxIdType]): The tax_id_type for the tax_id provided, required if tax_id provided
196
+ country_of_citizenship (Optional[str]): The country the user is a citizen
197
+ country_of_birth (Optional[str]): The country the user was born
198
+ country_of_tax_residence (Optional[str]): The country the user files taxes
199
+ visa_type (Optional[VisaType]): Only used to collect visa types for users residing in the USA.
200
+ visa_expiration_date (Optional[str]): The date of expiration for visa, Required if visa_type is set.
201
+ date_of_departure_from_usa (Optional[str]): Required if visa_type = B1 or B2
202
+ permanent_resident (Optional[bool]): Only used to collect permanent residence status in the USA.
203
+ funding_source (Optional[List[FundingSource]]): How the user will fund their account
204
+ annual_income_min (Optional[float]): The minimum of the user's income range
205
+ annual_income_max (Optional[float]): The maximum of the user's income range
206
+ liquid_net_worth_min (Optional[float]): The minimum of the user's liquid net worth range
207
+ liquid_net_worth_max (Optional[float]): The maximum of the user's liquid net worth range
208
+ total_net_worth_min (Optional[float]): The minimum of the user's total net worth range
209
+ total_net_worth_max (Optional[float]): The maximum of the user's total net worth range
210
+ """
211
+
212
+ given_name: Optional[str] = None
213
+ middle_name: Optional[str] = None
214
+ family_name: Optional[str] = None
215
+ visa_type: Optional[VisaType] = None
216
+ visa_expiration_date: Optional[str] = None
217
+ date_of_departure_from_usa: Optional[str] = None
218
+ permanent_resident: Optional[bool] = None
219
+ funding_source: Optional[List[FundingSource]] = None
220
+ annual_income_min: Optional[float] = None
221
+ annual_income_max: Optional[float] = None
222
+ liquid_net_worth_min: Optional[float] = None
223
+ liquid_net_worth_max: Optional[float] = None
224
+ total_net_worth_min: Optional[float] = None
225
+ total_net_worth_max: Optional[float] = None
226
+
227
+
228
+ class UpdatableDisclosures(Disclosures):
229
+ """
230
+ An extended version of Disclosures that has all fields as optional, so you don't need to specify all fields if you
231
+ only want to update a subset of them.
232
+
233
+ Attributes:
234
+ is_control_person (Optional[bool]): Whether user holds a controlling position in a publicly traded company
235
+ is_affiliated_exchange_or_finra (Optional[bool]): If user is affiliated with any exchanges or FINRA
236
+ is_politically_exposed (Optional[bool]): If user is politically exposed
237
+ immediate_family_exposed (Optional[bool]): If user’s immediate family member is either politically exposed or holds a control position.
238
+ employment_status (Optional[EmploymentStatus]): The employment status of the user
239
+ employer_name (Optional[str]): The user's employer's name, if any
240
+ employer_address (Optional[str]): The user's employer's address, if any
241
+ employment_position (Optional[str]): The user's employment position, if any
242
+ """
243
+
244
+ is_control_person: Optional[bool] = None
245
+ is_affiliated_exchange_or_finra: Optional[bool] = None
246
+ is_politically_exposed: Optional[bool] = None
247
+ immediate_family_exposed: Optional[bool] = None
248
+
249
+
250
+ class UpdatableTrustedContact(TrustedContact):
251
+ """
252
+ An extended version of TrustedContact that has all fields as optional, so you don't need to specify all fields if
253
+ you only want to update a subset of them.
254
+
255
+ Attributes:
256
+ given_name (Optional[str]): The first name of the user's trusted contact
257
+ family_name (Optional[str]): The last name of the user's trusted contact
258
+ email_address (Optional[str]): The email address of the user's trusted contact
259
+ phone_number (Optional[str]): The email address of the user's trusted contact
260
+ city (Optional[str]): The email address of the user's trusted contact
261
+ state (Optional[str]): The email address of the user's trusted contact
262
+ postal_code (Optional[str]): The email address of the user's trusted contact
263
+ country (Optional[str]): The email address of the user's trusted contact
264
+ """
265
+
266
+ # only need to override these 2 as other fields were already optional
267
+ given_name: Optional[str] = None
268
+ family_name: Optional[str] = None
269
+
270
+ # override the parent and set a new root field_validator that just allows all
271
+ @model_validator(mode="before")
272
+ def root_validator(cls, values: dict) -> dict:
273
+ """Override parent method to allow null contact info"""
274
+ return values
275
+
276
+
277
+ class UpdateAccountRequest(NonEmptyRequest):
278
+ """
279
+ Represents the data allowed in a request to update an Account. Note not all fields of an account
280
+ are currently modifiable so this model uses models that represent the subset of modifiable fields.
281
+
282
+ Attributes:
283
+ contact (Optional[UpdatableContact]): Contact details to update to
284
+ identity (Optional[UpdatableIdentity]): Identity details to update to
285
+ disclosures (Optional[UpdatableDisclosures]): Disclosure details to update to
286
+ trusted_contact (Optional[UpdatableTrustedContact]): TrustedContact details to update to
287
+ """
288
+
289
+ contact: Optional[UpdatableContact] = None
290
+ identity: Optional[UpdatableIdentity] = None
291
+ disclosures: Optional[UpdatableDisclosures] = None
292
+ trusted_contact: Optional[UpdatableTrustedContact] = None
293
+
294
+
295
+ class ListAccountsRequest(NonEmptyRequest):
296
+ """
297
+ Represents the values you can specify when making a request to list accounts
298
+
299
+ Attributes:
300
+ query (Optional[str]): Pass space-delimited tokens. The response will contain accounts that match with each of
301
+ the tokens (logical AND). A match means the token is present in either the account’s associated account number,
302
+ phone number, name, or e-mail address (logical OR).
303
+ created_before (Optional[datetime]): Accounts that were created before this date
304
+ created_after (Optional[datetime]): Accounts that were created after this date
305
+ status (Optional[AccountStatus]): Accounts that have their status field as one of these
306
+ sort (Sort, optional): The chronological order of response based on the submission time. Defaults to DESC.
307
+ entities (Optional[List[AccountEntities]]): By default, this endpoint doesn't return all information for each
308
+ account to save space in the response. This field lets you specify what additional information you want to be
309
+ included on each account.
310
+
311
+ ie, specifying [IDENTITY, CONTACT] would ensure that each returned account has its `identity` and `contact`
312
+ fields filled out.
313
+ """
314
+
315
+ query: Optional[str] = None
316
+ created_before: Optional[datetime] = None
317
+ created_after: Optional[datetime] = None
318
+ status: Optional[List[AccountStatus]] = None
319
+ sort: Sort
320
+ entities: Optional[List[AccountEntities]] = None
321
+
322
+ def __init__(self, *args, **kwargs):
323
+ # The api itself actually defaults to DESC, but this way our docs won't be incorrect if the api changes under us
324
+ if "sort" not in kwargs or kwargs["sort"] is None:
325
+ kwargs["sort"] = Sort.DESC
326
+
327
+ super().__init__(*args, **kwargs)
328
+
329
+
330
+ class GetAccountActivitiesRequest(NonEmptyRequest):
331
+ """
332
+ Represents the filtering values you can specify when getting AccountActivities for an Account
333
+
334
+ **Notes on pagination and the `page_size` and `page_token` fields**.
335
+
336
+ The BrokerClient::get_account_activities function by default will automatically handle the pagination of results
337
+ for you to get all results at once. However, if you're requesting a very large amount of results this can use a
338
+ large amount of memory and time to gather all the results. If you instead want to handle
339
+ pagination yourself `page_size` and `page_token` are how you would handle this.
340
+
341
+ Say you put in a request with `page_size` set to 4, you'll only get 4 results back to get
342
+ the next "page" of results you would set `page_token` to be the `id` field of the last Activity returned in the
343
+ result set.
344
+
345
+ This gets more indepth if you start specifying the `sort` field as well. If specified with a direction of Sort.DESC,
346
+ for example, the results will end before the activity with the specified ID. However, specified with a direction of
347
+ Sort.ASC, results will begin with the activity immediately after the one specified.
348
+
349
+ Also, to note if `date` is not specified, the default and maximum `page_size` value is 100. If `date` is specified,
350
+ the default behavior is to return all results, and there is no maximum page size; page size is still supported in
351
+ this state though.
352
+
353
+ Please see https://alpaca.markets/docs/api-references/broker-api/accounts/account-activities/#retrieving-account-activities
354
+ for more information
355
+
356
+ Attributes:
357
+ account_id (Optional[Union[UUID, str]]): Specifies to filter to only activities for this Account
358
+ activity_types (Optional[List[ActivityType]]): A list of ActivityType's to filter results down to
359
+ date (Optional[datetime]): Filter to Activities only on this date.
360
+ until (Optional[datetime]): Filter to Activities before this date. Cannot be used if `date` is also specified.
361
+ after (Optional[datetime]): Filter to Activities after this date. Cannot be used if `date` is also specified.
362
+ direction (Optional[Sort]): Which direction to sort results in. Defaults to Sort.DESC
363
+ page_size (Optional[int]): The maximum number of entries to return in the response
364
+ page_token (Optional[Union[UUID, str]]): If you're not using the built-in pagination this field is what you
365
+ would use to mark the end of the results of your last page.
366
+ """
367
+
368
+ account_id: Optional[Union[UUID, str]] = None
369
+ activity_types: Optional[List[ActivityType]] = None
370
+ date: Optional[datetime] = None
371
+ until: Optional[datetime] = None
372
+ after: Optional[datetime] = None
373
+ direction: Optional[Sort] = None
374
+ page_size: Optional[int] = None
375
+ page_token: Optional[Union[UUID, str]] = None
376
+
377
+ def __init__(self, *args, **kwargs):
378
+ if "account_id" in kwargs and type(kwargs["account_id"]) == str:
379
+ kwargs["account_id"] = UUID(kwargs["account_id"])
380
+
381
+ super().__init__(*args, **kwargs)
382
+
383
+ @model_validator(mode="before")
384
+ def root_validator(cls, values: dict) -> dict:
385
+ """Verify that certain conflicting params aren't set"""
386
+
387
+ date_set = "date" in values and values["date"] is not None
388
+ after_set = "after" in values and values["after"] is not None
389
+ until_set = "until" in values and values["until"] is not None
390
+
391
+ if date_set and after_set:
392
+ raise ValueError("Cannot set date and after at the same time")
393
+
394
+ if date_set and until_set:
395
+ raise ValueError("Cannot set date and until at the same time")
396
+
397
+ return values
398
+
399
+ @field_serializer("activity_types")
400
+ def serialize_activity_types(self, activity_types: Optional[List[ActivityType]]):
401
+ if activity_types:
402
+ return ",".join([t.value for t in activity_types])
403
+
404
+
405
+ # ############################## Documents ################################# #
406
+
407
+
408
+ class GetTradeDocumentsRequest(NonEmptyRequest):
409
+ """
410
+ Represents the various filters you can specify when making a call to get TradeDocuments for an Account
411
+
412
+ Attributes:
413
+ start (Optional[Union[date, str]]): Filter to TradeDocuments created after this Date. str values will attempt to
414
+ be upcast into date instances. Format must be in YYYY-MM-DD.
415
+ end (Optional[Union[date, str]]): Filter to TradeDocuments created before this Date. str values will attempt to
416
+ be upcast into date instances. Format must be in YYYY-MM-DD.
417
+ type (Optional[TradeDocumentType]): Filter to only these types of TradeDocuments
418
+ """
419
+
420
+ start: Optional[Union[date, str]] = None
421
+ end: Optional[Union[date, str]] = None
422
+ type: Optional[TradeDocumentType] = None
423
+
424
+ def __init__(self, **data) -> None:
425
+ if "start" in data and isinstance(data["start"], str):
426
+ data["start"] = date.fromisoformat(data["start"])
427
+
428
+ if "end" in data and isinstance(data["end"], str):
429
+ data["end"] = date.fromisoformat(data["end"])
430
+
431
+ super().__init__(**data)
432
+
433
+ @model_validator(mode="before")
434
+ def root_validator(cls, values: dict) -> dict:
435
+ if (
436
+ "start" in values
437
+ and values["start"] is not None
438
+ and "end" in values
439
+ and values["end"] is not None
440
+ and values["start"] > values["end"]
441
+ ):
442
+ raise ValueError("start must not be after end!!")
443
+
444
+ return values
445
+
446
+
447
+ class UploadDocumentRequest(NonEmptyRequest):
448
+ """
449
+ Attributes:
450
+ document_type (DocumentType): The type of document you are uploading
451
+ document_sub_type (Optional[UploadDocumentSubType]): If supported for the corresponding `document_type` this
452
+ field allows you to specify a sub type to be even more specific.
453
+ content (str): A string containing Base64 encoded data to upload.
454
+ mime_type (UploadDocumentMimeType): The mime type of the data in `content`
455
+ """
456
+
457
+ document_type: DocumentType
458
+ document_sub_type: Optional[UploadDocumentSubType] = None
459
+ content: str
460
+ mime_type: UploadDocumentMimeType
461
+
462
+ @model_validator(mode="before")
463
+ def root_validator(cls, values: dict) -> dict:
464
+ if values["document_type"] == DocumentType.W8BEN:
465
+ raise ValueError(
466
+ "Error please use the UploadW8BenDocument class for uploading W8BEN documents"
467
+ )
468
+
469
+ if values.get("document_sub_type", None) == UploadDocumentSubType.FORM_W8_BEN:
470
+ raise ValueError(
471
+ "Error please use the UploadW8BenDocument class for uploading W8BEN documents"
472
+ )
473
+
474
+ return values
475
+
476
+
477
+ # ############################## Banking and Transfers ################################# #
478
+
479
+
480
+ class CreateACHRelationshipRequest(NonEmptyRequest):
481
+ """
482
+ Attributes:
483
+ account_owner_name (str): The name of the ACH account owner for the relationship that is being created.
484
+ bank_account_type (BankAccountType): Specifies the type of bank account for the ACH relationship that is being
485
+ created.
486
+ bank_account_number (str): The bank account number associated with the ACH relationship.
487
+ bank_routing_number (str): THe bank routing number associated with the ACH relationship.
488
+ nickname (Optional[str]): Optionally specify a nickname to assign to the created ACH relationship.
489
+ """
490
+
491
+ account_owner_name: str
492
+ bank_account_type: BankAccountType
493
+ bank_account_number: str # TODO: Validate bank account number format.
494
+ bank_routing_number: str # TODO: Validate bank routing number format.
495
+ nickname: Optional[str] = None
496
+
497
+
498
+ class CreatePlaidRelationshipRequest(NonEmptyRequest):
499
+ """
500
+ This request is made following the Plaid bank account link user flow.
501
+
502
+ Upon the user completing their connection with Plaid, a public token specific to the user is returned by Plaid. This
503
+ token is used to get an Alpaca processor token via Plaid's /processor/token/create endpoint, which is subsequently
504
+ used by this endpoint to transfer the user's Plaid information to Alpaca.
505
+
506
+ Attributes:
507
+ processor_token (str): The processor token that is specific to Alpaca and was returned by Plaid.
508
+ """
509
+
510
+ processor_token: str
511
+
512
+
513
+ class CreateBankRequest(NonEmptyRequest):
514
+ """
515
+ Attributes:
516
+ name (str): The name of the recipient bank.
517
+ bank_code_type (IdentifierType): Specifies the type of the bank (international or domestic). See
518
+ enums.IdentifierType for more details.
519
+ bank_code (str): The 9-digit ABA routing number (domestic) or bank identifier code (BIC, international).
520
+ account_number (str): The bank account number.
521
+ country (Optional[str]): The country of the bank, if and only if creating an international bank account
522
+ connection.
523
+ state_province (Optional[str]): The state/province of the bank, if and only if creating an international bank
524
+ account connection.
525
+ postal_code (Optional[str]): The postal code of the bank, if and only if creating an international bank account
526
+ connection.
527
+ city (Optional[str]): The city of the bank, if and only if creating an international bank account connection.
528
+ street_address (Optional[str]): The street address of the bank, if and only if creating an international bank
529
+ account connection.
530
+ """
531
+
532
+ name: str
533
+ bank_code_type: IdentifierType
534
+ bank_code: str
535
+ account_number: str
536
+ country: Optional[str] = None
537
+ state_province: Optional[str] = None
538
+ postal_code: Optional[str] = None
539
+ city: Optional[str] = None
540
+ street_address: Optional[str] = None
541
+
542
+ @model_validator(mode="before")
543
+ def root_validator(cls, values: dict) -> dict:
544
+ if "bank_code_type" not in values:
545
+ # Bank code type was not valid, so a ValueError will be thrown regardless.
546
+ return values
547
+
548
+ international_parameters = [
549
+ "country",
550
+ "state_province",
551
+ "postal_code",
552
+ "city",
553
+ "street_address",
554
+ ]
555
+
556
+ bank_code_type = values["bank_code_type"]
557
+ if bank_code_type == IdentifierType.ABA:
558
+ for international_param in international_parameters:
559
+ if (
560
+ international_param in values
561
+ and values[international_param] is not None
562
+ ):
563
+ raise ValueError(
564
+ f"You may only specify the {international_param} for international bank accounts."
565
+ )
566
+ elif bank_code_type == IdentifierType.BIC:
567
+ for international_param in international_parameters:
568
+ if (
569
+ international_param not in values
570
+ or values[international_param] is None
571
+ ):
572
+ raise ValueError(
573
+ f"You must specify the {international_param} for international bank accounts."
574
+ )
575
+
576
+ return values
577
+
578
+
579
+ class _CreateTransferRequest(NonEmptyRequest):
580
+ """
581
+ Attributes:
582
+ amount (str): Amount of transfer, must be > 0. Any applicable fees will be deducted from this value.
583
+ direction (TransferDirection): Direction of the transfer.
584
+ timing (TransferTiming): Timing of the transfer.
585
+ fee_payment_method (Optional[FeePaymentMethod]): Determines how any applicable fees will be paid. Default value
586
+ is invoice.
587
+ """
588
+
589
+ amount: str
590
+ direction: TransferDirection
591
+ timing: TransferTiming
592
+ fee_payment_method: Optional[FeePaymentMethod] = None
593
+
594
+ @field_validator("amount")
595
+ def amount_must_be_positive(cls, value: str) -> str:
596
+ if float(value) <= 0:
597
+ raise ValueError("You must provide an amount > 0.")
598
+ return value
599
+
600
+
601
+ class CreateACHTransferRequest(_CreateTransferRequest):
602
+ """
603
+ Attributes:
604
+ transfer_type (TransferType): Type of the transfer.
605
+ relationship_id (Optional[UUID]): ID of the relationship to use for the transfer, required for ACH transfers.
606
+ """
607
+
608
+ relationship_id: UUID
609
+ transfer_type: TransferType = TransferType.ACH
610
+
611
+ @field_validator("transfer_type")
612
+ def transfer_type_must_be_ach(cls, value: TransferType) -> TransferType:
613
+ if value != TransferType.ACH:
614
+ raise ValueError(
615
+ "Transfer type must be TransferType.ACH for ACH transfer requests."
616
+ )
617
+ return value
618
+
619
+
620
+ class CreateBankTransferRequest(_CreateTransferRequest):
621
+ """
622
+ Attributes:
623
+ bank_id (UUID): ID of the bank to use for the transfer, required for wire transfers.
624
+ additional_information (Optional[str]): Additional wire transfer details.
625
+ """
626
+
627
+ bank_id: UUID
628
+ transfer_type: TransferType = TransferType.WIRE
629
+ additional_information: Optional[str] = None
630
+
631
+ @field_validator("transfer_type")
632
+ def transfer_type_must_be_wire(cls, value: TransferType) -> TransferType:
633
+ if value != TransferType.WIRE:
634
+ raise ValueError(
635
+ "Transfer type must be TransferType.WIRE for bank transfer requests."
636
+ )
637
+ return value
638
+
639
+
640
+ class GetTransfersRequest(NonEmptyRequest):
641
+ """
642
+ Attributes:
643
+ direction: Optionally filter for transfers of only a single TransferDirection.
644
+ """
645
+
646
+ direction: Optional[TransferDirection] = None
647
+ limit: Optional[int] = None
648
+ offset: Optional[int] = None
649
+
650
+
651
+ # ############################## Orders ################################# #
652
+
653
+
654
+ class OrderRequest(BaseOrderRequest):
655
+ """
656
+ See base alpaca.trading.requests.OrderRequest model for more information.
657
+
658
+ Attributes:
659
+ symbol (str): The symbol identifier for the asset being traded
660
+ qty (Optional[float]): The number of shares to trade. Fractional qty for stocks only with market orders.
661
+ notional (Optional[float]): The base currency value of the shares to trade. For stocks, only works with MarketOrders.
662
+ **Does not work with qty**.
663
+ side (OrderSide): Whether the order will buy or sell the asset.
664
+ type (OrderType): The execution logic type of the order (market, limit, etc).
665
+ time_in_force (TimeInForce): The expiration logic of the order.
666
+ extended_hours (Optional[float]): Whether the order can be executed during regular market hours.
667
+ client_order_id (Optional[float]): A string to identify which client submitted the order.
668
+ order_class (Optional[OrderClass]): The class of the order. Simple orders have no other legs.
669
+ take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
670
+ stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
671
+ commission (Optional[float]): The dollar value commission you want to charge the end user.
672
+ """
673
+
674
+ commission: Optional[float] = None
675
+ currency: Optional[SupportedCurrencies] = None # None = USD
676
+
677
+ @model_validator(mode="before")
678
+ def order_type_must_be_market_for_lct(
679
+ cls, values: Dict[str, Any]
680
+ ) -> Dict[str, Any]:
681
+ """
682
+ Order type must always be market if currency is not USD.
683
+ See https://alpaca.markets/docs/broker/integration/lct/#submit-stock-trade
684
+ """
685
+ if (
686
+ values.get("type") != OrderType.MARKET
687
+ and "currency" in values
688
+ and values.get("currency", SupportedCurrencies.USD)
689
+ != SupportedCurrencies.USD
690
+ ):
691
+ raise ValueError(
692
+ "Order type must be OrderType.MARKET if the order is in a local currency."
693
+ )
694
+ return values
695
+
696
+
697
+ class MarketOrderRequest(BaseMarketOrderRequest):
698
+ """
699
+ See base alpaca.trading.requests.MarketOrderRequest model for more information.
700
+
701
+ Attributes:
702
+ symbol (str): The symbol identifier for the asset being traded
703
+ qty (Optional[float]): The number of shares to trade. Fractional qty for stocks only with market orders.
704
+ notional (Optional[float]): The base currency value of the shares to trade. For stocks, only works with MarketOrders.
705
+ **Does not work with qty**.
706
+ side (OrderSide): Whether the order will buy or sell the asset.
707
+ type (OrderType): The execution logic type of the order (market, limit, etc).
708
+ time_in_force (TimeInForce): The expiration logic of the order.
709
+ extended_hours (Optional[float]): Whether the order can be executed during regular market hours.
710
+ client_order_id (Optional[float]): A string to identify which client submitted the order.
711
+ order_class (Optional[OrderClass]): The class of the order. Simple orders have no other legs.
712
+ take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
713
+ stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
714
+ commission (Optional[float]): The dollar value commission you want to charge the end user.
715
+ """
716
+
717
+ commission: Optional[float] = None
718
+
719
+
720
+ class LimitOrderRequest(BaseLimitOrderRequest):
721
+ """
722
+ See base alpaca.trading.requests.LimitOrderRequest model for more information.
723
+
724
+ Attributes:
725
+ symbol (str): The symbol identifier for the asset being traded
726
+ qty (Optional[float]): The number of shares to trade. Fractional qty for stocks only with market orders.
727
+ notional (Optional[float]): The base currency value of the shares to trade. For stocks, only works with MarketOrders.
728
+ **Does not work with qty**.
729
+ side (OrderSide): Whether the order will buy or sell the asset.
730
+ type (OrderType): The execution logic type of the order (market, limit, etc).
731
+ time_in_force (TimeInForce): The expiration logic of the order.
732
+ extended_hours (Optional[float]): Whether the order can be executed during regular market hours.
733
+ client_order_id (Optional[float]): A string to identify which client submitted the order.
734
+ order_class (Optional[OrderClass]): The class of the order. Simple orders have no other legs.
735
+ take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
736
+ stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
737
+ limit_price (float): The worst fill price for a limit or stop limit order.
738
+ commission (Optional[float]): The dollar value commission you want to charge the end user.
739
+ """
740
+
741
+ commission: Optional[float] = None
742
+
743
+
744
+ class StopOrderRequest(BaseStopOrderRequest):
745
+ """
746
+ See base alpaca.trading.requests.StopOrderRequest model for more information.
747
+
748
+ Attributes:
749
+ symbol (str): The symbol identifier for the asset being traded
750
+ qty (Optional[float]): The number of shares to trade. Fractional qty for stocks only with market orders.
751
+ notional (Optional[float]): The base currency value of the shares to trade. For stocks, only works with MarketOrders.
752
+ **Does not work with qty**.
753
+ side (OrderSide): Whether the order will buy or sell the asset.
754
+ type (OrderType): The execution logic type of the order (market, limit, etc).
755
+ time_in_force (TimeInForce): The expiration logic of the order.
756
+ extended_hours (Optional[float]): Whether the order can be executed during regular market hours.
757
+ client_order_id (Optional[float]): A string to identify which client submitted the order.
758
+ order_class (Optional[OrderClass]): The class of the order. Simple orders have no other legs.
759
+ take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
760
+ stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
761
+ stop_price (float): The price at which the stop order is converted to a market order or a stop limit
762
+ order is converted to a limit order.
763
+ commission (Optional[float]): The dollar value commission you want to charge the end user.
764
+ """
765
+
766
+ commission: Optional[float] = None
767
+
768
+
769
+ class StopLimitOrderRequest(BaseStopLimitOrderRequest):
770
+ """
771
+ See base alpaca.trading.requests.StopLimitOrderRequest model for more information.
772
+
773
+ Attributes:
774
+ symbol (str): The symbol identifier for the asset being traded
775
+ qty (Optional[float]): The number of shares to trade. Fractional qty for stocks only with market orders.
776
+ notional (Optional[float]): The base currency value of the shares to trade. For stocks, only works with MarketOrders.
777
+ **Does not work with qty**.
778
+ side (OrderSide): Whether the order will buy or sell the asset.
779
+ type (OrderType): The execution logic type of the order (market, limit, etc).
780
+ time_in_force (TimeInForce): The expiration logic of the order.
781
+ extended_hours (Optional[float]): Whether the order can be executed during regular market hours.
782
+ client_order_id (Optional[float]): A string to identify which client submitted the order.
783
+ order_class (Optional[OrderClass]): The class of the order. Simple orders have no other legs.
784
+ take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
785
+ stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
786
+ stop_price (float): The price at which the stop order is converted to a market order or a stop limit
787
+ order is converted to a limit order.
788
+ limit_price (float): The worst fill price for a limit or stop limit order.
789
+ commission (Optional[float]): The dollar value commission you want to charge the end user
790
+ """
791
+
792
+ commission: Optional[float] = None
793
+
794
+
795
+ class TrailingStopOrderRequest(BaseTrailingStopOrderRequest):
796
+ """
797
+ See base alpaca.trading.requests.TrailingStopOrderRequest model for more information.
798
+
799
+ Attributes:
800
+ symbol (str): The symbol identifier for the asset being traded
801
+ qty (Optional[float]): The number of shares to trade. Fractional qty for stocks only with market orders.
802
+ notional (Optional[float]): The base currency value of the shares to trade. For stocks, only works with MarketOrders.
803
+ **Does not work with qty**.
804
+ side (OrderSide): Whether the order will buy or sell the asset.
805
+ type (OrderType): The execution logic type of the order (market, limit, etc).
806
+ time_in_force (TimeInForce): The expiration logic of the order.
807
+ extended_hours (Optional[float]): Whether the order can be executed during regular market hours.
808
+ client_order_id (Optional[float]): A string to identify which client submitted the order.
809
+ order_class (Optional[OrderClass]): The class of the order. Simple orders have no other legs.
810
+ take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
811
+ stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
812
+ trail_price (Optional[float]): The absolute price difference by which the trailing stop will trail.
813
+ trail_percent (Optional[float]): The percent price difference by which the trailing stop will trail.
814
+ commission (Optional[float]): The dollar value commission you want to charge the end user.
815
+ """
816
+
817
+ commission: Optional[float] = None
818
+
819
+
820
+ # ############################## Journals ################################# #
821
+
822
+
823
+ class CreateJournalRequest(NonEmptyRequest):
824
+ """
825
+ Data for request to initiate a single journal.
826
+
827
+ Attributes:
828
+ to_account (UUID): The account ID that received the journal.
829
+ from_account (UUID): The account ID that initiates the journal.
830
+ entry_type (JournalEntryType): Whether the journal is a cash or security journal.
831
+ symbol (Optional[str]): For security journals, the symbol identifier of the security being journaled.
832
+ qty (Optional[float]): For security journals, the quantity of the security being journaled.
833
+ amount (Optional[float]): For cash journals, the total cash amount journaled in USD.
834
+ description (Optional[str]): Journal description. It can include fixtures for sandbox API.
835
+ transmitter_name (Optional[str]): For cash journals, travel rule related name info.
836
+ transmitter_account_number (Optional[str]): For cash journals, travel rule account number info.
837
+ transmitter_address (Optional[str]): For cash journals, travel rule related address info.
838
+ transmitter_financial_institution (Optional[str]): For cash journals, travel rule related institution info.
839
+ transmitter_timestamp (Optional[str]): For cash journals, travel rule related timestamp info.
840
+ """
841
+
842
+ from_account: UUID
843
+ entry_type: JournalEntryType
844
+ to_account: UUID
845
+ amount: Optional[float] = None
846
+ symbol: Optional[str] = None
847
+ qty: Optional[float] = None
848
+ description: Optional[str] = None
849
+ transmitter_name: Optional[str] = None
850
+ transmitter_account_number: Optional[str] = None
851
+ transmitter_address: Optional[str] = None
852
+ transmitter_financial_institution: Optional[str] = None
853
+ transmitter_timestamp: Optional[str] = None
854
+ currency: Optional[SupportedCurrencies] = None # None = USD
855
+
856
+ @model_validator(mode="before")
857
+ def root_validator(cls, values: dict) -> dict:
858
+ entry_type = values.get("entry_type")
859
+ symbol = values.get("symbol")
860
+ qty = values.get("qty")
861
+ amount = values.get("amount")
862
+
863
+ # amount is for cash journals, symbol and qty are for security journals
864
+ # they are mutually exclusive
865
+ if entry_type is not None and entry_type == JournalEntryType.CASH:
866
+ if symbol or qty:
867
+ raise ValueError("Symbol and qty are reserved for security journals.")
868
+
869
+ if not amount:
870
+ raise ValueError("Cash journals must contain an amount to transfer.")
871
+
872
+ if entry_type is not None and entry_type == JournalEntryType.SECURITY:
873
+ if amount:
874
+ raise ValueError("Amount is reserved for cash journals.")
875
+
876
+ if not symbol or not qty:
877
+ raise ValueError(
878
+ "Security journals must contain a symbol and corresponding qty to transfer."
879
+ )
880
+
881
+ return values
882
+
883
+
884
+ class BatchJournalRequestEntry(NonEmptyRequest):
885
+ """
886
+ Entry in batch journal request.
887
+
888
+ Attributes:
889
+ to_account (UUID): Account to fund in batch journal request.
890
+ amount (Union[str, float]): The cash amount in USD to fund by.
891
+ description (Optional[str]): Journal description.
892
+ transmitter_name (Optional[str]): For cash journals, travel rule related name info.
893
+ transmitter_account_number (Optional[str]): For cash journals, travel rule account number info.
894
+ transmitter_address (Optional[str]): For cash journals, travel rule related address info.
895
+ transmitter_financial_institution (Optional[str]): For cash journals, travel rule related institution info.
896
+ transmitter_timestamp (Optional[str]): For cash journals, travel rule related timestamp info.
897
+ """
898
+
899
+ to_account: UUID
900
+ amount: Union[str, float]
901
+ description: Optional[str] = None
902
+ transmitter_name: Optional[str] = None
903
+ transmitter_account_number: Optional[str] = None
904
+ transmitter_address: Optional[str] = None
905
+ transmitter_financial_institution: Optional[str] = None
906
+ transmitter_timestamp: Optional[str] = None
907
+
908
+
909
+ class ReverseBatchJournalRequestEntry(NonEmptyRequest):
910
+ """
911
+ Entry in reverse batch journal request.
912
+
913
+ Attributes:
914
+ from_account (UUID): Account from fund in batch journal request.
915
+ amount (Union[str, float]): The cash amount in USD to fund by.
916
+ description (Optional[str]): Journal description.
917
+ transmitter_name (Optional[str]): For cash journals, travel rule related name info.
918
+ transmitter_account_number (Optional[str]): For cash journals, travel rule account number info.
919
+ transmitter_address (Optional[str]): For cash journals, travel rule related address info.
920
+ transmitter_financial_institution (Optional[str]): For cash journals, travel rule related institution info.
921
+ transmitter_timestamp (Optional[str]): For cash journals, travel rule related timestamp info.
922
+ """
923
+
924
+ from_account: UUID
925
+ amount: Union[str, float]
926
+ description: Optional[str] = None
927
+ transmitter_name: Optional[str] = None
928
+ transmitter_account_number: Optional[str] = None
929
+ transmitter_address: Optional[str] = None
930
+ transmitter_financial_institution: Optional[str] = None
931
+ transmitter_timestamp: Optional[str] = None
932
+
933
+
934
+ class CreateBatchJournalRequest(NonEmptyRequest):
935
+ """
936
+ This model represents the fields you can specify when creating
937
+ a request of many Journals out of one account to many others at once.
938
+
939
+ Currently, batch journals are only enabled on cash journals.
940
+
941
+ Attributes:
942
+ entry_type (JournalEntryType): The type of journal transfer.
943
+ from_account (UUID): The originator of funds. Most likely is your Sweep Firm Account
944
+ entries (List[BatchJournalRequestEntry]): List of journals to execute.
945
+ """
946
+
947
+ entry_type: JournalEntryType
948
+ from_account: UUID
949
+ entries: List[BatchJournalRequestEntry]
950
+
951
+
952
+ class CreateReverseBatchJournalRequest(NonEmptyRequest):
953
+ """
954
+ This model represents the fields you can specify when creating
955
+ a request of many Journals into one account from many other accounts at once.
956
+
957
+ Currently, reverse batch journals are only enabled on cash journals.
958
+
959
+ Attributes:
960
+ entry_type (JournalEntryType): The type of journal transfer.
961
+ to_account (UUID): The destination of funds. Most likely is your Sweep Firm Account
962
+ entries (List[BatchJournalRequestEntry]): List of journals to execute.
963
+ """
964
+
965
+ entry_type: JournalEntryType
966
+ to_account: UUID
967
+ entries: List[ReverseBatchJournalRequestEntry]
968
+
969
+
970
+ class GetJournalsRequest(NonEmptyRequest):
971
+ """
972
+ This model represents the fields you can specify when querying from the list of all journals.
973
+
974
+ Attributes:
975
+ after (Optional[date]): Journal creation dates after this date.
976
+ before (Optional[date]): Journal creation dates before this date.
977
+ status (Optional[JournalStatus]): Only journals with this status.
978
+ entry_type (Optional[JournalEntryType]): Only journals with this entry type.
979
+ to_account (Optional[UUID]): Only journals to this account.
980
+ from_account (Optional[UUID]): Only journals from this account.
981
+ """
982
+
983
+ after: Optional[date] = None
984
+ before: Optional[date] = None
985
+ status: Optional[JournalStatus] = None
986
+ entry_type: Optional[JournalEntryType] = None
987
+ to_account: Optional[UUID] = None
988
+ from_account: Optional[UUID] = None
989
+
990
+
991
+ class GetEventsRequest(NonEmptyRequest):
992
+ id: Optional[str] = None
993
+ since: Optional[Union[date, str]] = None
994
+ until: Optional[Union[date, str]] = None
995
+ since_id: Optional[int] = None
996
+ until_id: Optional[int] = None
997
+
998
+
999
+ # ############################## Rebalancing ################################# #
1000
+
1001
+
1002
+ class Weight(BaseModel):
1003
+ """
1004
+ Weight model.
1005
+
1006
+ https://docs.alpaca.markets/reference/post-v1-rebalancing-portfolios
1007
+ """
1008
+
1009
+ type: WeightType
1010
+ symbol: Optional[str] = None
1011
+ percent: float
1012
+
1013
+ @field_validator("percent")
1014
+ def percent_must_be_positive(cls, value: float) -> float:
1015
+ """Validate and round the percent field to 2 decimal places."""
1016
+ if value <= 0:
1017
+ raise ValueError("You must provide an amount > 0.")
1018
+ return round(value, 2)
1019
+
1020
+ @model_validator(mode="before")
1021
+ def validator(cls, values: dict) -> dict:
1022
+ """Verify that the symbol is provided when the weights type is asset."""
1023
+ if (
1024
+ values["type"] == WeightType.ASSET.value
1025
+ and values.get("symbol", None) is None
1026
+ ):
1027
+ raise ValueError
1028
+ return values
1029
+
1030
+
1031
+ class RebalancingConditions(BaseModel):
1032
+ """
1033
+ Rebalancing conditions model.
1034
+
1035
+ https://docs.alpaca.markets/reference/post-v1-rebalancing-portfolios
1036
+ """
1037
+
1038
+ type: RebalancingConditionsType
1039
+ sub_type: Union[DriftBandSubType, CalendarSubType]
1040
+ percent: Optional[float] = None
1041
+ day: Optional[str] = None
1042
+
1043
+
1044
+ class CreatePortfolioRequest(NonEmptyRequest):
1045
+ """
1046
+ Portfolio request model.
1047
+
1048
+ https://docs.alpaca.markets/reference/post-v1-rebalancing-portfolios
1049
+ """
1050
+
1051
+ name: str
1052
+ description: str
1053
+ weights: List[Weight]
1054
+ cooldown_days: int
1055
+ rebalance_conditions: Optional[List[RebalancingConditions]] = None
1056
+
1057
+
1058
+ class UpdatePortfolioRequest(NonEmptyRequest):
1059
+ """
1060
+ Portfolio request update model.
1061
+
1062
+ https://docs.alpaca.markets/reference/patch-v1-rebalancing-portfolios-portfolio_id-1
1063
+ """
1064
+
1065
+ name: Optional[str] = None
1066
+ description: Optional[str] = None
1067
+ weights: Optional[List[Weight]] = None
1068
+ cooldown_days: Optional[int] = None
1069
+ rebalance_conditions: Optional[List[RebalancingConditions]] = None
1070
+
1071
+
1072
+ class GetPortfoliosRequest(NonEmptyRequest):
1073
+ """
1074
+ Get portfolios request query parameters.
1075
+
1076
+ https://docs.alpaca.markets/reference/get-v1-rebalancing-portfolios
1077
+ """
1078
+
1079
+ name: Optional[str] = None
1080
+ description: Optional[str] = None
1081
+ symbol: Optional[str] = None
1082
+ portfolio_id: Optional[UUID] = None
1083
+ status: Optional[PortfolioStatus] = None
1084
+
1085
+
1086
+ class CreateSubscriptionRequest(NonEmptyRequest):
1087
+ """
1088
+ Subscription request model.
1089
+
1090
+ https://docs.alpaca.markets/reference/post-v1-rebalancing-subscriptions-1
1091
+ """
1092
+
1093
+ account_id: UUID
1094
+ portfolio_id: UUID
1095
+
1096
+
1097
+ class GetSubscriptionsRequest(NonEmptyRequest):
1098
+ """
1099
+ Get subscriptions request query parameters.
1100
+
1101
+ https://docs.alpaca.markets/reference/get-v1-rebalancing-subscriptions-1
1102
+ """
1103
+
1104
+ account_id: Optional[UUID] = None
1105
+ portfolio_id: Optional[UUID] = None
1106
+ limit: Optional[int] = None
1107
+ page_token: Optional[str] = None
1108
+
1109
+
1110
+ class CreateRunRequest(NonEmptyRequest):
1111
+ """
1112
+ Manually creates a rebalancing run.
1113
+
1114
+ https://docs.alpaca.markets/reference/post-v1-rebalancing-runs
1115
+ """
1116
+
1117
+ account_id: UUID
1118
+ type: RunType
1119
+ weights: List[Weight]
1120
+
1121
+
1122
+ class GetRunsRequest(NonEmptyRequest):
1123
+ """
1124
+ Get runs request query parameters.
1125
+
1126
+ https://docs.alpaca.markets/reference/get-v1-rebalancing-runs
1127
+ """
1128
+
1129
+ account_id: Optional[UUID] = None
1130
+ type: Optional[RunType] = None
1131
+ limit: Optional[int] = None
1132
+
1133
+
1134
+ class CreateOptionExerciseRequest(NonEmptyRequest):
1135
+ commission: Optional[float] = None