etrade-python 1.0.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.
- etrade_python/__init__.py +264 -0
- etrade_python/_dates.py +152 -0
- etrade_python/_version.py +3 -0
- etrade_python/accounts/__init__.py +31 -0
- etrade_python/accounts/models.py +204 -0
- etrade_python/accounts/service.py +136 -0
- etrade_python/alerts/__init__.py +23 -0
- etrade_python/alerts/models.py +180 -0
- etrade_python/alerts/service.py +128 -0
- etrade_python/auth/__init__.py +31 -0
- etrade_python/auth/credentials.py +85 -0
- etrade_python/auth/oauth.py +268 -0
- etrade_python/auth/session.py +154 -0
- etrade_python/auth/stores/__init__.py +7 -0
- etrade_python/auth/stores/base.py +18 -0
- etrade_python/auth/stores/keyring.py +49 -0
- etrade_python/auth/stores/memory.py +19 -0
- etrade_python/cli/__init__.py +1 -0
- etrade_python/cli/app.py +1623 -0
- etrade_python/client.py +84 -0
- etrade_python/config.py +43 -0
- etrade_python/enums.py +19 -0
- etrade_python/exceptions.py +68 -0
- etrade_python/market/__init__.py +59 -0
- etrade_python/market/models.py +811 -0
- etrade_python/market/service.py +168 -0
- etrade_python/orders/__init__.py +107 -0
- etrade_python/orders/builders.py +617 -0
- etrade_python/orders/models.py +1009 -0
- etrade_python/orders/service.py +259 -0
- etrade_python/orders/values.py +67 -0
- etrade_python/portfolio/__init__.py +35 -0
- etrade_python/portfolio/models.py +429 -0
- etrade_python/portfolio/service.py +99 -0
- etrade_python/py.typed +0 -0
- etrade_python/transactions/__init__.py +27 -0
- etrade_python/transactions/models.py +201 -0
- etrade_python/transactions/service.py +138 -0
- etrade_python/transport/__init__.py +7 -0
- etrade_python/transport/auth.py +17 -0
- etrade_python/transport/http.py +240 -0
- etrade_python/transport/logging.py +38 -0
- etrade_python/transport/response.py +243 -0
- etrade_python/transport/retry.py +75 -0
- etrade_python-1.0.0.dist-info/METADATA +513 -0
- etrade_python-1.0.0.dist-info/RECORD +49 -0
- etrade_python-1.0.0.dist-info/WHEEL +4 -0
- etrade_python-1.0.0.dist-info/entry_points.txt +2 -0
- etrade_python-1.0.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: etrade-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: An unofficial, typed, async-first E*TRADE API client
|
|
5
|
+
Project-URL: Repository, https://github.com/kevinrider/etrade-python
|
|
6
|
+
Project-URL: Issues, https://github.com/kevinrider/etrade-python/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/kevinrider/etrade-python/blob/main/CHANGELOG.md
|
|
8
|
+
Author: etrade-python contributors
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: httpx<1,>=0.28
|
|
21
|
+
Requires-Dist: keyring<26,>=25
|
|
22
|
+
Requires-Dist: pydantic-settings<3,>=2.7
|
|
23
|
+
Requires-Dist: pydantic<3,>=2.10
|
|
24
|
+
Requires-Dist: typer<1,>=0.20
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build<2,>=1.2; extra == 'dev'
|
|
27
|
+
Requires-Dist: pyright<2,>=1.1.400; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio<2,>=1; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov<8,>=6; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest<10,>=8.3; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff<1,>=0.11; extra == 'dev'
|
|
32
|
+
Requires-Dist: twine<7,>=6; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# etrade-python
|
|
36
|
+
|
|
37
|
+
An unofficial, typed, async-first Python client for the E*TRADE REST API.
|
|
38
|
+
|
|
39
|
+
`etrade-python` provides typed request and response models, OAuth 1.0a session
|
|
40
|
+
management, and shared async networking for Python applications, CLIs, background
|
|
41
|
+
workers, web services, workflow engines, and notebooks. Requires Python 3.11+.
|
|
42
|
+
|
|
43
|
+
The package is prepared for its first stable release, **1.0.0**. The PyPI
|
|
44
|
+
installation instructions below apply once the release is published.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- Namespaced Accounts, Portfolio, Transactions, Market, Orders, and Alerts services.
|
|
49
|
+
- Async HTTPX transport shared across services, with injectable clients and transports.
|
|
50
|
+
- Pydantic request/response models, Decimal monetary values, and date/datetime fields.
|
|
51
|
+
- Explicit sandbox and production settings; sandbox is the default.
|
|
52
|
+
- OAuth authorization, inactivity renewal, daily expiration, and pluggable credential stores.
|
|
53
|
+
- Separate order preview and placement, validated requests, and an order builder.
|
|
54
|
+
- Pagination helpers, structured exceptions, sanitized diagnostics, and conservative retries.
|
|
55
|
+
- Diagnostic CLI and interactive orders demo.
|
|
56
|
+
|
|
57
|
+
Responses are JSON only. Unknown response fields are discarded rather than exposed
|
|
58
|
+
as an untyped metadata dictionary. Typed models support JSON serialization and schema
|
|
59
|
+
inspection through Pydantic.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
From a source checkout, install into an activated Python 3.11+ virtual environment:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
python -m pip install .
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Once published on PyPI:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
python -m pip install etrade-python
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The distribution name is `etrade-python`; the import package is `etrade_python`.
|
|
76
|
+
The installation also provides the `etrade` command.
|
|
77
|
+
|
|
78
|
+
## API Credentials and Configuration
|
|
79
|
+
|
|
80
|
+
Obtain a consumer key and consumer secret through the
|
|
81
|
+
[official E*TRADE getting-started guide](https://developer.etrade.com/getting-started).
|
|
82
|
+
Sandbox and production require separate keys. Use credentials issued for the
|
|
83
|
+
configured environment.
|
|
84
|
+
|
|
85
|
+
Export these variables before using the SDK or CLI:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
export ETRADE_CONSUMER_KEY='your-sandbox-consumer-key'
|
|
89
|
+
export ETRADE_CONSUMER_SECRET='your-sandbox-consumer-secret'
|
|
90
|
+
export ETRADE_ENVIRONMENT=sandbox
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`ETRADE_ENVIRONMENT` accepts `sandbox` or `production`. Sandbox is the default;
|
|
94
|
+
production must be selected explicitly. Optional settings are
|
|
95
|
+
`ETRADE_REQUEST_TIMEOUT_SECONDS` (default `30`) and
|
|
96
|
+
`ETRADE_INACTIVITY_BUFFER_SECONDS` (default `300`).
|
|
97
|
+
|
|
98
|
+
`ETradeClient.from_environment()` and the CLI read environment variables. They do
|
|
99
|
+
not automatically load `.env`. For explicit `.env` loading in an application:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from etrade_python import ETradeClient, ETradeSettings
|
|
103
|
+
|
|
104
|
+
settings = ETradeSettings(_env_file=".env")
|
|
105
|
+
client = ETradeClient(settings)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Copy `.env.example` as a starting point and replace its fake values.
|
|
109
|
+
|
|
110
|
+
## Authentication
|
|
111
|
+
|
|
112
|
+
Bootstrap authentication using the CLI:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
etrade auth login
|
|
116
|
+
etrade auth status
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Login prints a browser authorization URL. Open it, authorize the application, and
|
|
120
|
+
enter the verifier at the hidden prompt. The CLI exchanges it for access credentials
|
|
121
|
+
and saves them in the system keyring. Use the same environment, consumer key, and
|
|
122
|
+
profile when accessing those credentials from an application.
|
|
123
|
+
|
|
124
|
+
Custom profiles are optional:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
etrade auth login --profile personal
|
|
128
|
+
etrade auth status --profile personal
|
|
129
|
+
etrade auth renew --profile personal
|
|
130
|
+
etrade auth revoke --profile personal
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Pass `profile="personal"` to `ETradeClient(settings, profile="personal")` in SDK
|
|
134
|
+
code. `from_environment()` uses the default profile.
|
|
135
|
+
|
|
136
|
+
E*TRADE tokens become inactive after two hours without requests and expire at
|
|
137
|
+
midnight US Eastern time. The session manager renews locally inactive tokens
|
|
138
|
+
before an API request; expired tokens require a new login. Explicit `auth renew`
|
|
139
|
+
contacts the broker even when the token appears active locally.
|
|
140
|
+
[Official token lifecycle](https://apisb.etrade.com/docs/api/authorization/renew_access_token.html).
|
|
141
|
+
|
|
142
|
+
`auth status` reports a local lifecycle estimate, not broker verification.
|
|
143
|
+
|
|
144
|
+
## Accounts and Portfolio
|
|
145
|
+
|
|
146
|
+
After logging in with the CLI, save this as a Python script and run it with the
|
|
147
|
+
same environment variables:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
import asyncio
|
|
151
|
+
|
|
152
|
+
from etrade_python import ETradeClient
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def main() -> None:
|
|
156
|
+
async with ETradeClient.from_environment() as client:
|
|
157
|
+
response = await client.accounts.list()
|
|
158
|
+
if not response.accounts:
|
|
159
|
+
print("No accounts returned.")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
account = response.accounts[0]
|
|
163
|
+
account_id_key = account.account_id_key
|
|
164
|
+
balance = await client.accounts.get_balance(account_id_key)
|
|
165
|
+
portfolio = await client.portfolio.get_positions(account_id_key)
|
|
166
|
+
print(balance.model_dump_json(indent=2))
|
|
167
|
+
print(portfolio.model_dump_json(indent=2))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
asyncio.run(main())
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Account operations take `account_id_key`, the broker's routing identifier returned
|
|
174
|
+
by account listing, rather than the numeric `account_id`. Output can contain
|
|
175
|
+
sensitive account information; treat it accordingly.
|
|
176
|
+
|
|
177
|
+
## Market Data and Concurrent Requests
|
|
178
|
+
|
|
179
|
+
Independent requests can run concurrently through the shared async client:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
import asyncio
|
|
183
|
+
|
|
184
|
+
from etrade_python import ETradeClient
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
async def main() -> None:
|
|
188
|
+
async with ETradeClient.from_environment() as client:
|
|
189
|
+
quote, chain = await asyncio.gather(
|
|
190
|
+
client.market.get_quote("AAPL"),
|
|
191
|
+
client.market.get_option_chain("AAPL"),
|
|
192
|
+
)
|
|
193
|
+
print(quote.model_dump_json(indent=2))
|
|
194
|
+
print(chain.model_dump_json(indent=2))
|
|
195
|
+
|
|
196
|
+
products = await client.market.lookup_product("Bank of America")
|
|
197
|
+
print(products.model_dump_json(indent=2))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
asyncio.run(main())
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
For a specific expiration, use a date returned by the broker rather than guessing:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
import asyncio
|
|
207
|
+
|
|
208
|
+
from etrade_python import ETradeClient, OptionChainRequest
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def main() -> None:
|
|
212
|
+
async with ETradeClient.from_environment() as client:
|
|
213
|
+
response = await client.market.get_option_expirations("AAPL")
|
|
214
|
+
if not response.expiration_dates:
|
|
215
|
+
print("No expirations returned.")
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
expiration = response.expiration_dates[0]
|
|
219
|
+
chain = await client.market.get_option_chain(
|
|
220
|
+
"AAPL",
|
|
221
|
+
OptionChainRequest(
|
|
222
|
+
expiry_year=expiration.year,
|
|
223
|
+
expiry_month=expiration.month,
|
|
224
|
+
expiry_day=expiration.day,
|
|
225
|
+
chain_type="CALLPUT",
|
|
226
|
+
no_of_strikes=5,
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
print(chain.model_dump_json(indent=2))
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
asyncio.run(main())
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Pagination
|
|
236
|
+
|
|
237
|
+
Single-page methods return typed response models. For automatic pagination, use
|
|
238
|
+
`portfolio.iter_positions()`, `transactions.list_all()`, and `orders.list_all()`.
|
|
239
|
+
These are async iterators; despite the `list_all` name, they do not eagerly return
|
|
240
|
+
one combined list.
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
import asyncio
|
|
244
|
+
|
|
245
|
+
from etrade_python import ETradeClient, TransactionsRequest
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
async def main() -> None:
|
|
249
|
+
async with ETradeClient.from_environment() as client:
|
|
250
|
+
response = await client.accounts.list()
|
|
251
|
+
if not response.accounts:
|
|
252
|
+
return
|
|
253
|
+
account_id_key = response.accounts[0].account_id_key
|
|
254
|
+
|
|
255
|
+
async for transaction in client.transactions.list_all(
|
|
256
|
+
account_id_key, TransactionsRequest(count=50)
|
|
257
|
+
):
|
|
258
|
+
print(transaction.model_dump_json())
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
asyncio.run(main())
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Transaction requests default to descending order. Automatic transaction pagination
|
|
265
|
+
removes repeated boundary transactions and requires `count >= 2`; the single-page
|
|
266
|
+
`transactions.list()` method permits `count=1`. Pagination helpers reject repeated
|
|
267
|
+
continuation values instead of looping indefinitely.
|
|
268
|
+
|
|
269
|
+
## Order Builder and Preview
|
|
270
|
+
|
|
271
|
+
The builder produces the typed requests accepted by the Orders service. This
|
|
272
|
+
example previews an equity limit order; it does not place it:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
import asyncio
|
|
276
|
+
from decimal import Decimal
|
|
277
|
+
from uuid import uuid4
|
|
278
|
+
|
|
279
|
+
from etrade_python import ETradeClient, OrderBuilder
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
async def main() -> None:
|
|
283
|
+
async with ETradeClient.from_environment() as client:
|
|
284
|
+
response = await client.accounts.list()
|
|
285
|
+
if not response.accounts:
|
|
286
|
+
return
|
|
287
|
+
account_id_key = response.accounts[0].account_id_key
|
|
288
|
+
|
|
289
|
+
builder = (
|
|
290
|
+
OrderBuilder.for_account(account_id_key)
|
|
291
|
+
.client_order_id(uuid4().hex[:20])
|
|
292
|
+
.equity_limit("AAPL", action="BUY", quantity=1, limit_price=Decimal("1.00"))
|
|
293
|
+
)
|
|
294
|
+
preview = await client.orders.preview(account_id_key, builder.build_preview_request())
|
|
295
|
+
print(preview.model_dump_json(indent=2))
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
asyncio.run(main())
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The limit price is illustrative, not a trading recommendation. Preview acceptance
|
|
302
|
+
depends on the account and broker validations.
|
|
303
|
+
|
|
304
|
+
The builder supports equity, single-option, vertical spread, three-leg, iron-condor,
|
|
305
|
+
and buy-write requests. Low-level request models remain available for direct use.
|
|
306
|
+
Preview and placement stay separate: after reviewing an actual preview response,
|
|
307
|
+
pass its IDs to `builder.build_place_request(preview_ids=...)` and submit that request
|
|
308
|
+
with `client.orders.place(account_id_key, request)`. SDK placement methods do not
|
|
309
|
+
provide a CLI-style confirmation prompt; the application must control submission.
|
|
310
|
+
|
|
311
|
+
Changes use `orders.preview_change()` and `orders.place_change()`; cancellation uses
|
|
312
|
+
`orders.cancel()`. Production placement, changes, and cancellations affect real
|
|
313
|
+
brokerage accounts. Order mutations are not automatically retried. If a request
|
|
314
|
+
fails after submission, reconcile its broker outcome before resubmitting.
|
|
315
|
+
|
|
316
|
+
## Error Handling
|
|
317
|
+
|
|
318
|
+
The library raises structured E*TRADE exceptions rather than exposing raw HTTPX
|
|
319
|
+
status exceptions as its primary API:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
import asyncio
|
|
323
|
+
|
|
324
|
+
from etrade_python import (
|
|
325
|
+
AuthenticationRequired,
|
|
326
|
+
AuthorizationExpired,
|
|
327
|
+
ETradeApiError,
|
|
328
|
+
ETradeClient,
|
|
329
|
+
ETradeError,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
async def main() -> None:
|
|
334
|
+
try:
|
|
335
|
+
async with ETradeClient.from_environment() as client:
|
|
336
|
+
quote = await client.market.get_quote("AAPL")
|
|
337
|
+
print(quote.model_dump_json(indent=2))
|
|
338
|
+
except (AuthenticationRequired, AuthorizationExpired):
|
|
339
|
+
print("Run etrade auth login with the configured environment and profile.")
|
|
340
|
+
except ETradeApiError as error:
|
|
341
|
+
print(f"Broker request failed: HTTP {error.status_code}, code {error.broker_code}")
|
|
342
|
+
except ETradeError:
|
|
343
|
+
print("The client could not complete the request.")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
asyncio.run(main())
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
API exceptions expose `status_code`, `broker_code`, `broker_message`, and `request_id`
|
|
350
|
+
when available. Diagnostics redact known request secrets, but broker messages can
|
|
351
|
+
still contain personal information; avoid logging them wholesale. Invalid direct
|
|
352
|
+
Pydantic model construction raises Pydantic validation errors.
|
|
353
|
+
|
|
354
|
+
## Credential Storage
|
|
355
|
+
|
|
356
|
+
`ETradeClient` defaults to `KeyringCredentialStore`. The operating-system keyring
|
|
357
|
+
backend determines storage protection; there is no automatic plaintext fallback.
|
|
358
|
+
Credentials are isolated by environment, consumer-key SHA-256 fingerprint, and
|
|
359
|
+
profile.
|
|
360
|
+
|
|
361
|
+
To use process-local memory storage:
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
from etrade_python import ETradeClient, ETradeSettings
|
|
365
|
+
from etrade_python.auth import MemoryCredentialStore
|
|
366
|
+
|
|
367
|
+
store = MemoryCredentialStore()
|
|
368
|
+
client = ETradeClient(ETradeSettings(), credential_store=store)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Memory storage does not load credentials saved by the CLI and is lost when the
|
|
372
|
+
process exits. Use `client.oauth.get_request_token()`,
|
|
373
|
+
`client.oauth.get_authorization_url()`, and `client.oauth.exchange_verifier()` for
|
|
374
|
+
application-managed authorization, then `await client.session.save(credentials)`.
|
|
375
|
+
Always close the client after use. Applications can implement the async
|
|
376
|
+
`CredentialStore` protocol for other persistence systems.
|
|
377
|
+
|
|
378
|
+
## Diagnostic CLI and Orders Demo
|
|
379
|
+
|
|
380
|
+
Use `etrade --help` or `etrade COMMAND --help` to inspect available commands.
|
|
381
|
+
Add `--json` to read/preview commands for typed JSON output; it is optional.
|
|
382
|
+
|
|
383
|
+
```sh
|
|
384
|
+
etrade accounts list
|
|
385
|
+
etrade accounts balance ACCOUNT_ID_KEY
|
|
386
|
+
etrade portfolio positions ACCOUNT_ID_KEY
|
|
387
|
+
etrade transactions list ACCOUNT_ID_KEY --count 50 --json
|
|
388
|
+
etrade transactions get ACCOUNT_ID_KEY TRANSACTION_ID
|
|
389
|
+
etrade market quote AAPL
|
|
390
|
+
etrade market quotes AAPL MSFT
|
|
391
|
+
etrade market lookup "Bank of America"
|
|
392
|
+
etrade market option-expirations AAPL
|
|
393
|
+
etrade market option-chain AAPL
|
|
394
|
+
etrade orders list ACCOUNT_ID_KEY
|
|
395
|
+
etrade alerts list
|
|
396
|
+
etrade alerts get ALERT_ID
|
|
397
|
+
etrade alerts delete ALERT_ID --confirm-delete
|
|
398
|
+
etrade orders demo
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
The orders demo begins with account selection and supports equity, single-option,
|
|
402
|
+
vertical spread, three-leg, iron-condor, and buy-write scenarios. It retrieves
|
|
403
|
+
expirations and strikes from market data and suggests prices from quotes. Expirations
|
|
404
|
+
appear ten per page, with `n`/`p` navigation; the initial page shows the monthly
|
|
405
|
+
default when one is available. Prices and broker responses still require review.
|
|
406
|
+
|
|
407
|
+
The demo walks through preview, optional placement, change, and cancellation with
|
|
408
|
+
explicit confirmations. File-based order commands are also available:
|
|
409
|
+
|
|
410
|
+
```sh
|
|
411
|
+
etrade orders preview ACCOUNT_ID_KEY preview-order.json
|
|
412
|
+
etrade orders place ACCOUNT_ID_KEY place-order.json --confirm-live-order
|
|
413
|
+
etrade orders preview-change ACCOUNT_ID_KEY ORDER_ID preview-order.json
|
|
414
|
+
etrade orders place-change ACCOUNT_ID_KEY ORDER_ID place-order.json --confirm-live-order
|
|
415
|
+
etrade orders cancel ACCOUNT_ID_KEY ORDER_ID --confirm-live-order
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Order JSON files use the broker request envelope emitted by the request models.
|
|
419
|
+
Account commands print routing identifiers for manual testing; protect their output.
|
|
420
|
+
Login intentionally prints the authorization URL needed for the browser flow.
|
|
421
|
+
Order mutation commands require `--confirm-live-order`, and alert deletion requires
|
|
422
|
+
`--confirm-delete`.
|
|
423
|
+
|
|
424
|
+
## Development
|
|
425
|
+
|
|
426
|
+
Use uv and the Makefile from the repository root:
|
|
427
|
+
|
|
428
|
+
```sh
|
|
429
|
+
make install
|
|
430
|
+
make check
|
|
431
|
+
make package-check
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
`make install` synchronizes the environment and development dependencies.
|
|
435
|
+
`make check` runs lint, formatting checks, type checking, and offline tests.
|
|
436
|
+
`make package-check` builds the wheel/source distribution and validates metadata.
|
|
437
|
+
|
|
438
|
+
Individual targets include `make lint`, `make format`, `make format-check`,
|
|
439
|
+
`make type`, and `make test`. `make venv-reset` deletes `.venv` and recreates it
|
|
440
|
+
with Python 3.12; the library itself supports Python 3.11+. `make build` recreates
|
|
441
|
+
`dist`, and `make clean` removes generated build and cache artifacts.
|
|
442
|
+
|
|
443
|
+
If make is unavailable:
|
|
444
|
+
|
|
445
|
+
```sh
|
|
446
|
+
uv sync --extra dev
|
|
447
|
+
uv run ruff check .
|
|
448
|
+
uv run ruff format --check .
|
|
449
|
+
uv run pyright --pythonpath .venv/bin/python
|
|
450
|
+
uv run pytest
|
|
451
|
+
uv build
|
|
452
|
+
uv run python -m twine check dist/*
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
CI checks Python 3.11–3.14, enforces at least 90% combined statement/branch coverage,
|
|
456
|
+
and verifies wheel installation outside the checkout. Push and pull-request CI
|
|
457
|
+
does not publish. A separate release workflow publishes stable GitHub releases
|
|
458
|
+
using PyPI Trusted Publishing once the publisher is configured. See
|
|
459
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for contribution and package verification instructions.
|
|
460
|
+
|
|
461
|
+
## API Coverage
|
|
462
|
+
|
|
463
|
+
The documented endpoint families below are implemented and covered by offline
|
|
464
|
+
contract tests.
|
|
465
|
+
|
|
466
|
+
Endpoint paths are relative to `/v1` on the sandbox or production API host.
|
|
467
|
+
OAuth paths are relative to `https://api.etrade.com` for both environments. JSON
|
|
468
|
+
suffixes select response format and do not create separate endpoints.
|
|
469
|
+
|
|
470
|
+
| Family | Official endpoint | Service method | Request | Response |
|
|
471
|
+
| --- | --- | --- | --- | --- |
|
|
472
|
+
| OAuth | [GET /oauth/request_token](https://apisb.etrade.com/docs/api/authorization/request_token.html) | `OAuthClient.get_request_token` | - | RequestToken |
|
|
473
|
+
| OAuth | [GET https://us.etrade.com/e/t/etws/authorize](https://apisb.etrade.com/docs/api/authorization/authorize.html) | `OAuthClient.get_authorization_url` | RequestToken | AuthorizationUrl |
|
|
474
|
+
| OAuth | [GET /oauth/access_token](https://apisb.etrade.com/docs/api/authorization/get_access_token.html) | `OAuthClient.exchange_verifier` | RequestToken + verifier | ETradeCredentials |
|
|
475
|
+
| OAuth | [GET /oauth/renew_access_token](https://apisb.etrade.com/docs/api/authorization/renew_access_token.html) | `OAuthClient.renew_access_token` | ETradeCredentials | RenewalResult |
|
|
476
|
+
| OAuth | [GET /oauth/revoke_access_token](https://apisb.etrade.com/docs/api/authorization/revoke_access_token.html) | `OAuthClient.revoke_access_token` | ETradeCredentials | RevocationResult |
|
|
477
|
+
| Accounts | [GET /accounts/list](https://apisb.etrade.com/docs/api/account/api-account-v1.html) | `accounts.list` | - | AccountListResponse |
|
|
478
|
+
| Accounts | [GET /accounts/{accountIdKey}/balance](https://apisb.etrade.com/docs/api/account/api-balance-v1.html) | `accounts.get_balance` | AccountBalanceRequest | AccountBalanceResponse |
|
|
479
|
+
| Portfolio | [GET /accounts/{accountIdKey}/portfolio](https://apisb.etrade.com/docs/api/account/api-portfolio-v1.html) | `portfolio.get_positions / iter_positions` | PortfolioRequest | PortfolioResponse including PositionLot |
|
|
480
|
+
| Transactions | [GET /accounts/{accountIdKey}/transactions](https://apisb.etrade.com/docs/api/account/api-transaction-v1.html) | `transactions.list` | TransactionsRequest | TransactionsResponse |
|
|
481
|
+
| Transactions | [GET /accounts/{accountIdKey}/transactions/{tranid}](https://apisb.etrade.com/docs/api/account/api-transaction-v1.html) | `transactions.get` | TransactionDetailsRequest | TransactionDetailsResponse |
|
|
482
|
+
| Market | [GET /market/quote/{symbols}](https://apisb.etrade.com/docs/api/market/api-quote-v1.html) | `market.get_quote / get_quotes` | QuotesRequest | Quote / QuotesResponse |
|
|
483
|
+
| Market | [GET /market/lookup/{search}](https://apisb.etrade.com/docs/api/market/api-market-v1.html) | `market.lookup_product` | ProductLookupRequest | ProductLookupResponse |
|
|
484
|
+
| Market | [GET /market/optionexpiredate](https://apisb.etrade.com/docs/api/market/api-market-v1.html) | `market.get_option_expirations` | OptionExpirationsRequest | OptionExpirationsResponse |
|
|
485
|
+
| Market | [GET /market/optionchains](https://apisb.etrade.com/docs/api/market/api-market-v1.html) | `market.get_option_chain` | OptionChainRequest | OptionChainResponse |
|
|
486
|
+
| Orders | [GET /accounts/{accountIdKey}/orders](https://apisb.etrade.com/docs/api/order/api-order-v1.html) | `orders.list / list_all` | OrdersRequest | OrdersResponse |
|
|
487
|
+
| Orders | [POST /accounts/{accountIdKey}/orders/preview](https://apisb.etrade.com/docs/api/order/api-order-v1.html) | `orders.preview` | PreviewOrderRequest | PreviewOrderResponse |
|
|
488
|
+
| Orders | [POST /accounts/{accountIdKey}/orders/place](https://apisb.etrade.com/docs/api/order/api-order-v1.html) | `orders.place` | PlaceOrderRequest | PlaceOrderResponse |
|
|
489
|
+
| Orders | [PUT /accounts/{accountIdKey}/orders/{orderId}/change/preview](https://apisb.etrade.com/docs/api/order/api-order-v1.html) | `orders.preview_change` | PreviewOrderRequest | PreviewOrderResponse |
|
|
490
|
+
| Orders | [PUT /accounts/{accountIdKey}/orders/{orderId}/change/place](https://apisb.etrade.com/docs/api/order/api-order-v1.html) | `orders.place_change` | PlaceOrderRequest | PlaceOrderResponse |
|
|
491
|
+
| Orders | [PUT /accounts/{accountIdKey}/orders/cancel](https://apisb.etrade.com/docs/api/order/api-order-v1.html) | `orders.cancel` | CancelOrderRequest | CancelOrderResponse |
|
|
492
|
+
| Alerts | [GET /user/alerts](https://apisb.etrade.com/docs/api/user/api-alert-v1.html) | `alerts.list` | AlertsRequest | AlertsResponse |
|
|
493
|
+
| Alerts | [GET /user/alerts/{id}](https://apisb.etrade.com/docs/api/user/api-alert-v1.html) | `alerts.get` | AlertDetailsRequest | AlertDetailsResponse |
|
|
494
|
+
| Alerts | [DELETE /user/alerts/{alert_id_list}](https://apisb.etrade.com/docs/api/user/api-alert-v1.html) | `alerts.delete` | alert ID list | DeleteAlertsResponse |
|
|
495
|
+
|
|
496
|
+
## Safety and License
|
|
497
|
+
|
|
498
|
+
This project is not affiliated with, endorsed by, or supported by Morgan Stanley
|
|
499
|
+
or E*TRADE. It is a software API client, not financial advice.
|
|
500
|
+
|
|
501
|
+
Production trading methods affect real brokerage accounts. Start in sandbox,
|
|
502
|
+
review request details and broker confirmations, and keep secrets and sensitive
|
|
503
|
+
account output out of source control and public logs. Read the
|
|
504
|
+
[security policy](SECURITY.md) for storage, logging, and mutation considerations.
|
|
505
|
+
|
|
506
|
+
Licensed under [Apache-2.0](LICENSE).
|
|
507
|
+
|
|
508
|
+
## Further Reading
|
|
509
|
+
|
|
510
|
+
- [Official E*TRADE developer documentation](https://developer.etrade.com/)
|
|
511
|
+
- [Changelog](CHANGELOG.md)
|
|
512
|
+
- [Contributing guide](CONTRIBUTING.md)
|
|
513
|
+
- [Security policy](SECURITY.md)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
etrade_python/__init__.py,sha256=YeTxdrXY3kz_W_njtE5_z4td7wCpUVFavZA2kZyxdsY,5963
|
|
2
|
+
etrade_python/_dates.py,sha256=6I7QmElS3e5jipJSVkY0lrBNed1i9eCIjsMhFPicTlU,4614
|
|
3
|
+
etrade_python/_version.py,sha256=wVzvdZEqZCvsp3GrIZI-Oh8FDnb35FkUIcgt3r4K1U4,94
|
|
4
|
+
etrade_python/client.py,sha256=5I5frcyDDnPfIfmYRYMBnHxsQbqJh1mPufegbDHEe70,3439
|
|
5
|
+
etrade_python/config.py,sha256=JbuPHoGijKHO9eN37WIrWNZaGpxspNBcNj9wIab8oHY,1699
|
|
6
|
+
etrade_python/enums.py,sha256=ashvh8PXge21mUeGj7cJfYuQlxZp5V_vGuiIF4hCjiw,519
|
|
7
|
+
etrade_python/exceptions.py,sha256=2qIZExdQj1VlGCEzUrLCGD6kTwM5Yb02khMiFJiwyg4,1896
|
|
8
|
+
etrade_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
etrade_python/accounts/__init__.py,sha256=0KtBaDl7UVin-Gl-qnwKhjBaWpiKU2XsIoLakCv60JM,657
|
|
10
|
+
etrade_python/accounts/models.py,sha256=9gILGHLW6V_8TOoVtgBk-TMRblpog8aC09ma2vNNM8c,9753
|
|
11
|
+
etrade_python/accounts/service.py,sha256=dbs8C_qavZCtGhkPuwSkAa-bK1thwd3viYoA0r1WuBM,4943
|
|
12
|
+
etrade_python/alerts/__init__.py,sha256=aZ3fwW4iKyWFFki6pq5BmSEHkD7he70KnyzI7W0bEuY,473
|
|
13
|
+
etrade_python/alerts/models.py,sha256=-hLI_0WO7wymOIWF3dWjq3royq6minN_Bx_32DM-Te8,5646
|
|
14
|
+
etrade_python/alerts/service.py,sha256=96y9KwMsyMJVipenicS0NlIQ6mgziQv9bnUuDbzcQ_I,5084
|
|
15
|
+
etrade_python/auth/__init__.py,sha256=vtXjjZw8rAP2MBLhBj0csw422DLMUVo-k8YqK3TPiNU,750
|
|
16
|
+
etrade_python/auth/credentials.py,sha256=1yF2lr6Qj2xuBBBnWiZdn2QUIs3qOjSO4NNgC-N27Gc,2873
|
|
17
|
+
etrade_python/auth/oauth.py,sha256=kFrLH83uf-cDe8MJycKNHCaUw8dXpuAn8GJzLNTK4F0,10494
|
|
18
|
+
etrade_python/auth/session.py,sha256=2WdfWkHt6UGgKO6Jn4teDkXwTQJ4fp2dK_kC5258oA8,6226
|
|
19
|
+
etrade_python/auth/stores/__init__.py,sha256=d2cCDnDxeXsBOqmLa0A1lDaQWr_GEzXrTlLqDuVYvmM,318
|
|
20
|
+
etrade_python/auth/stores/base.py,sha256=XXbWAoYUf-MumiPybpAd-jv_8pMZMYxPRlu-FwdQctA,591
|
|
21
|
+
etrade_python/auth/stores/keyring.py,sha256=QWEgEKg-41fbTYMu9RtrdsEFmgO9jAxkMRMCb12LfXI,1909
|
|
22
|
+
etrade_python/auth/stores/memory.py,sha256=lnzjQe-rJgJIL4t-6_QHSizVhFJQv1DFilC8EVKNO3g,645
|
|
23
|
+
etrade_python/cli/__init__.py,sha256=0ouz55BJFQtDyuOsMAIaI-RI0vCWqZfos6OF1lDc_js,38
|
|
24
|
+
etrade_python/cli/app.py,sha256=Hxs1iDI4hMgC6hWyEplgYdKulB1tLpHG_NUfbvpdag0,64833
|
|
25
|
+
etrade_python/market/__init__.py,sha256=uEfAqe1ff4tkpbSk-YNMEhwLn-oVQUK6-_gSFkqM5iU,1296
|
|
26
|
+
etrade_python/market/models.py,sha256=Qumrafwxi5qDymdsU5_idJZHkL50YUVQPI-mlHmGc1g,32501
|
|
27
|
+
etrade_python/market/service.py,sha256=q3vQNzaVo4LG6TiIw_seexvPqJrhMI9HeNGTXZ3cph8,6683
|
|
28
|
+
etrade_python/orders/__init__.py,sha256=xeweUYxQACo98neg1o_XVkv08HS7sHeXui8J3WKYk_w,2264
|
|
29
|
+
etrade_python/orders/builders.py,sha256=FSjfO1gYHrZOyUNmPR5FJAOv7HVnkkUHKobXVQF6zSs,22001
|
|
30
|
+
etrade_python/orders/models.py,sha256=Zxo7evowCVWatIe2X-Di1PN3-cj_CwTWeewwjYE8HM4,40742
|
|
31
|
+
etrade_python/orders/service.py,sha256=3lf-_-_eMDynk7v4N3EgNLsJL6r7-fUiUeo-6_QWnYY,10881
|
|
32
|
+
etrade_python/orders/values.py,sha256=z6BK_Zho0_sKsG5rcDMRP4xyByWrGQREcY1b9pb_zMo,1659
|
|
33
|
+
etrade_python/portfolio/__init__.py,sha256=bYXq3mJac-46ORmd6s7LoM-DpUWNu19cTPlFnRCB6Cw,720
|
|
34
|
+
etrade_python/portfolio/models.py,sha256=GVfjj2wuSoFB1Dygy-diH3uPn8J8uPpYkdJKFlhp1Mw,20118
|
|
35
|
+
etrade_python/portfolio/service.py,sha256=0CbRYSToY9wnB4rf1rfK6zNS4PrNiWGh-jnzqTGzIR4,3968
|
|
36
|
+
etrade_python/transactions/__init__.py,sha256=geOcmt3KHQYQgbPx68-ajD25hnVD_tVicqQNWaJ9jsQ,663
|
|
37
|
+
etrade_python/transactions/models.py,sha256=u1mBMVBzPy4cvKOCsjRQaLWY2xnylWBZb1X_dlDIbg0,7549
|
|
38
|
+
etrade_python/transactions/service.py,sha256=A5k_LLAzioqgmrGR-OH228RBk21BKHUSKJBo3W7clLA,5495
|
|
39
|
+
etrade_python/transport/__init__.py,sha256=VaGqhPNrNnskVEwAgllywxW4pHE_9Kk0K8j5rf7o_T4,342
|
|
40
|
+
etrade_python/transport/auth.py,sha256=eLXwnwdD7K5URBuyf9cdV-I4o2s-EYM7PUXic5sFGXs,669
|
|
41
|
+
etrade_python/transport/http.py,sha256=O56r0q589CE-AoHlOJcykrzq9cvfxtVhg5Ri7R4nwV8,10204
|
|
42
|
+
etrade_python/transport/logging.py,sha256=743RBiSJPxLVM1aacs-nGWP_bV8SvPsBfOoHXTwir28,1129
|
|
43
|
+
etrade_python/transport/response.py,sha256=BxOlKtphuwsdTEpjO-OkEL-Y-0GEaIShHtkMNNQKEPI,8905
|
|
44
|
+
etrade_python/transport/retry.py,sha256=L7KzV-AoLPrnnttfIRGzhSh3U5rpuU8a8mnB_p4v_Z0,2627
|
|
45
|
+
etrade_python-1.0.0.dist-info/METADATA,sha256=U3dh0vFjg9wN05QACd1P8nQyMIo4_2OipsYEJzgkyL4,21795
|
|
46
|
+
etrade_python-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
47
|
+
etrade_python-1.0.0.dist-info/entry_points.txt,sha256=f4i6DI09N1yZdkyEiE48FA9ErB_FcvHCZd4Pm6TKukQ,53
|
|
48
|
+
etrade_python-1.0.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
49
|
+
etrade_python-1.0.0.dist-info/RECORD,,
|