fidelity-trader-api 0.1.0__tar.gz
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.
- fidelity_trader_api-0.1.0/.claude/agents/capture-analyst.md +79 -0
- fidelity_trader_api-0.1.0/.claude/agents/docker-builder.md +237 -0
- fidelity_trader_api-0.1.0/.claude/agents/route-builder.md +181 -0
- fidelity_trader_api-0.1.0/.claude/agents/sdk-implementer.md +168 -0
- fidelity_trader_api-0.1.0/.claude/agents/service-reviewer.md +157 -0
- fidelity_trader_api-0.1.0/.claude/agents/service-scaffolder.md +158 -0
- fidelity_trader_api-0.1.0/.claude/agents/service-tester.md +209 -0
- fidelity_trader_api-0.1.0/.claude/agents/streaming-builder.md +223 -0
- fidelity_trader_api-0.1.0/.dockerignore +14 -0
- fidelity_trader_api-0.1.0/.github/workflows/ci.yml +31 -0
- fidelity_trader_api-0.1.0/.github/workflows/publish.yml +60 -0
- fidelity_trader_api-0.1.0/.gitignore +12 -0
- fidelity_trader_api-0.1.0/CLAUDE.md +159 -0
- fidelity_trader_api-0.1.0/LICENSE +191 -0
- fidelity_trader_api-0.1.0/PKG-INFO +830 -0
- fidelity_trader_api-0.1.0/README.md +792 -0
- fidelity_trader_api-0.1.0/data/ftservice.db +0 -0
- fidelity_trader_api-0.1.0/docker/.env.example +35 -0
- fidelity_trader_api-0.1.0/docker/Dockerfile +26 -0
- fidelity_trader_api-0.1.0/docker/docker-compose.yml +19 -0
- fidelity_trader_api-0.1.0/docs/BACKLOG.md +175 -0
- fidelity_trader_api-0.1.0/docs/DECISIONS.md +159 -0
- fidelity_trader_api-0.1.0/docs/PRODUCT_VISION.md +259 -0
- fidelity_trader_api-0.1.0/docs/SERVICE_PLAN.md +618 -0
- fidelity_trader_api-0.1.0/docs/captures/2026-03-30-websocket-streaming.md +137 -0
- fidelity_trader_api-0.1.0/docs/superpowers/plans/2026-03-30-fidelity-trader-sdk.md +2096 -0
- fidelity_trader_api-0.1.0/examples/full_walkthrough.py +822 -0
- fidelity_trader_api-0.1.0/examples/live_streaming.py +136 -0
- fidelity_trader_api-0.1.0/examples/live_test.py +108 -0
- fidelity_trader_api-0.1.0/examples/login.py +8 -0
- fidelity_trader_api-0.1.0/examples/mdds_experiment.py +222 -0
- fidelity_trader_api-0.1.0/examples/test_all_data_types.py +225 -0
- fidelity_trader_api-0.1.0/pyproject.toml +67 -0
- fidelity_trader_api-0.1.0/service/__init__.py +3 -0
- fidelity_trader_api-0.1.0/service/__main__.py +13 -0
- fidelity_trader_api-0.1.0/service/app.py +144 -0
- fidelity_trader_api-0.1.0/service/auth/__init__.py +0 -0
- fidelity_trader_api-0.1.0/service/auth/api_key.py +22 -0
- fidelity_trader_api-0.1.0/service/auth/middleware.py +64 -0
- fidelity_trader_api-0.1.0/service/config.py +17 -0
- fidelity_trader_api-0.1.0/service/dependencies.py +31 -0
- fidelity_trader_api-0.1.0/service/models/__init__.py +0 -0
- fidelity_trader_api-0.1.0/service/models/requests.py +43 -0
- fidelity_trader_api-0.1.0/service/models/responses.py +39 -0
- fidelity_trader_api-0.1.0/service/routes/__init__.py +0 -0
- fidelity_trader_api-0.1.0/service/routes/accounts.py +92 -0
- fidelity_trader_api-0.1.0/service/routes/auth.py +62 -0
- fidelity_trader_api-0.1.0/service/routes/market_data.py +71 -0
- fidelity_trader_api-0.1.0/service/routes/orders.py +187 -0
- fidelity_trader_api-0.1.0/service/routes/preferences.py +59 -0
- fidelity_trader_api-0.1.0/service/routes/reference.py +26 -0
- fidelity_trader_api-0.1.0/service/routes/research.py +79 -0
- fidelity_trader_api-0.1.0/service/routes/service.py +28 -0
- fidelity_trader_api-0.1.0/service/routes/streaming.py +64 -0
- fidelity_trader_api-0.1.0/service/routes/watchlists.py +109 -0
- fidelity_trader_api-0.1.0/service/session/__init__.py +0 -0
- fidelity_trader_api-0.1.0/service/session/keepalive.py +48 -0
- fidelity_trader_api-0.1.0/service/session/manager.py +77 -0
- fidelity_trader_api-0.1.0/service/session/store.py +114 -0
- fidelity_trader_api-0.1.0/service/streaming/__init__.py +0 -0
- fidelity_trader_api-0.1.0/service/streaming/manager.py +202 -0
- fidelity_trader_api-0.1.0/service/streaming/sse.py +55 -0
- fidelity_trader_api-0.1.0/service/streaming/ws.py +53 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/__init__.py +22 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/_http.py +75 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/alerts/__init__.py +4 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/alerts/price_triggers.py +140 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/alerts/subscription.py +147 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/async_client.py +216 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/auth/__init__.py +3 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/auth/auto_refresh.py +120 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/auth/security_context.py +26 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/auth/session.py +211 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/auth/session_keepalive.py +38 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/__init__.py +3 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_app.py +67 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_auth.py +145 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_config.py +25 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_errors.py +50 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_market_data.py +187 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_options.py +303 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_orders.py +293 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_output.py +109 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_portfolio.py +191 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_research.py +135 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_session.py +139 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/cli/_stream.py +204 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/client.py +162 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/credentials.py +182 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/exceptions.py +21 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/market_data/__init__.py +4 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/market_data/chart.py +105 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/market_data/fastquote.py +48 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/__init__.py +3 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/_parsers.py +39 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/account.py +15 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/account_detail.py +119 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/alerts.py +196 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/analytics.py +87 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/auth.py +40 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/available_market.py +97 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/balance.py +407 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/cancel_order.py +49 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/cancel_replace.py +263 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/chart.py +96 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/closed_position.py +252 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/conditional_order.py +357 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/equity_order.py +271 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/fastquote.py +222 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/holiday_calendar.py +42 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/loaned_securities.py +115 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/option_order.py +355 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/option_summary.py +268 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/order.py +182 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/position.py +206 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/preferences.py +47 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/price_trigger.py +214 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/research.py +118 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/screener.py +93 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/search.py +33 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/security_context.py +69 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/single_option_order.py +284 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/staged_order.py +80 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/streaming.py +13 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/tax_lot.py +134 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/transaction.py +153 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/models/watchlist.py +121 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/orders/__init__.py +8 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/orders/cancel.py +61 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/orders/cancel_replace.py +68 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/orders/conditional.py +73 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/orders/equity.py +65 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/orders/options.py +75 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/orders/single_option.py +71 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/orders/staged.py +69 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/orders/status.py +49 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/portfolio/__init__.py +7 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/portfolio/accounts.py +52 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/portfolio/balances.py +74 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/portfolio/closed_positions.py +68 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/portfolio/loaned_securities.py +29 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/portfolio/option_summary.py +37 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/portfolio/positions.py +63 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/portfolio/tax_lots.py +34 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/portfolio/transactions.py +68 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/reference/__init__.py +0 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/reference/holiday_calendar.py +26 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/reference/markets.py +29 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/research/__init__.py +6 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/research/analytics.py +49 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/research/data.py +44 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/research/screener.py +111 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/research/search.py +25 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/retry.py +105 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/settings/__init__.py +0 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/settings/preferences.py +89 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/streaming/__init__.py +4 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/streaming/mdds.py +330 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/streaming/mdds_fields.py +152 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/streaming/news.py +25 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/trading/__init__.py +0 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/watchlists/__init__.py +3 -0
- fidelity_trader_api-0.1.0/src/fidelity_trader/watchlists/watchlists.py +88 -0
- fidelity_trader_api-0.1.0/tests/__init__.py +0 -0
- fidelity_trader_api-0.1.0/tests/conftest.py +30 -0
- fidelity_trader_api-0.1.0/tests/test_account_discovery.py +501 -0
- fidelity_trader_api-0.1.0/tests/test_alerts.py +728 -0
- fidelity_trader_api-0.1.0/tests/test_analytics.py +465 -0
- fidelity_trader_api-0.1.0/tests/test_async_client.py +329 -0
- fidelity_trader_api-0.1.0/tests/test_auth.py +80 -0
- fidelity_trader_api-0.1.0/tests/test_auto_refresh.py +288 -0
- fidelity_trader_api-0.1.0/tests/test_available_markets.py +459 -0
- fidelity_trader_api-0.1.0/tests/test_balances.py +1153 -0
- fidelity_trader_api-0.1.0/tests/test_cancel_orders.py +189 -0
- fidelity_trader_api-0.1.0/tests/test_cancel_replace.py +766 -0
- fidelity_trader_api-0.1.0/tests/test_chart.py +452 -0
- fidelity_trader_api-0.1.0/tests/test_cli.py +425 -0
- fidelity_trader_api-0.1.0/tests/test_cli_market.py +398 -0
- fidelity_trader_api-0.1.0/tests/test_cli_orders.py +588 -0
- fidelity_trader_api-0.1.0/tests/test_client.py +280 -0
- fidelity_trader_api-0.1.0/tests/test_closed_positions.py +673 -0
- fidelity_trader_api-0.1.0/tests/test_conditional_orders.py +822 -0
- fidelity_trader_api-0.1.0/tests/test_csrf.py +38 -0
- fidelity_trader_api-0.1.0/tests/test_dry_run.py +265 -0
- fidelity_trader_api-0.1.0/tests/test_equity_orders.py +486 -0
- fidelity_trader_api-0.1.0/tests/test_fastquote.py +378 -0
- fidelity_trader_api-0.1.0/tests/test_holiday_calendar.py +293 -0
- fidelity_trader_api-0.1.0/tests/test_http.py +30 -0
- fidelity_trader_api-0.1.0/tests/test_loaned_securities.py +224 -0
- fidelity_trader_api-0.1.0/tests/test_mdds.py +1241 -0
- fidelity_trader_api-0.1.0/tests/test_models.py +137 -0
- fidelity_trader_api-0.1.0/tests/test_option_orders.py +583 -0
- fidelity_trader_api-0.1.0/tests/test_option_summary.py +640 -0
- fidelity_trader_api-0.1.0/tests/test_orders.py +581 -0
- fidelity_trader_api-0.1.0/tests/test_positions.py +501 -0
- fidelity_trader_api-0.1.0/tests/test_preferences.py +221 -0
- fidelity_trader_api-0.1.0/tests/test_price_triggers.py +880 -0
- fidelity_trader_api-0.1.0/tests/test_research.py +506 -0
- fidelity_trader_api-0.1.0/tests/test_retry.py +406 -0
- fidelity_trader_api-0.1.0/tests/test_screener.py +572 -0
- fidelity_trader_api-0.1.0/tests/test_search.py +324 -0
- fidelity_trader_api-0.1.0/tests/test_security_context.py +173 -0
- fidelity_trader_api-0.1.0/tests/test_service_routes_core.py +305 -0
- fidelity_trader_api-0.1.0/tests/test_service_routes_data.py +297 -0
- fidelity_trader_api-0.1.0/tests/test_service_scaffold.py +355 -0
- fidelity_trader_api-0.1.0/tests/test_service_streaming.py +416 -0
- fidelity_trader_api-0.1.0/tests/test_session_keepalive.py +238 -0
- fidelity_trader_api-0.1.0/tests/test_single_option_orders.py +646 -0
- fidelity_trader_api-0.1.0/tests/test_staged_orders.py +301 -0
- fidelity_trader_api-0.1.0/tests/test_streaming.py +140 -0
- fidelity_trader_api-0.1.0/tests/test_tax_lots.py +399 -0
- fidelity_trader_api-0.1.0/tests/test_transactions.py +548 -0
- fidelity_trader_api-0.1.0/tests/test_watchlists.py +645 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: capture-analyst
|
|
3
|
+
description: Analyzes mitmproxy capture files to discover and document Fidelity API endpoints. Use when the user has completed a new mitmproxy capture session and needs endpoints extracted, documented, and queued for implementation.
|
|
4
|
+
tools: Read, Glob, Grep, Bash, Write
|
|
5
|
+
model: inherit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You analyze mitmproxy capture files to discover and document Fidelity Trader+ API endpoints.
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
This project is the `fidelity-trader-sdk` — an unofficial Python SDK that replicates Fidelity Trader+ desktop API calls via reverse-engineered mitmproxy captures. The SDK currently has **31 API modules** across 7+ hosts. Your job is to analyze new captures, extract undocumented endpoints, and prepare implementation specs.
|
|
13
|
+
|
|
14
|
+
## Capture Files
|
|
15
|
+
|
|
16
|
+
Stored in `~/fidelity_*.flow`. Current inventory:
|
|
17
|
+
- `fidelity_capture.flow` — Login, positions, balances, accounts, preferences
|
|
18
|
+
- `fidelity_portfolio_capture.flow` — Transactions, option summary, closed positions, loaned securities, watchlists
|
|
19
|
+
- `fidelity_market_hours_capture.flow` — Orders (equity/option/cancel), tax lots, available markets, analytics, chart, montage
|
|
20
|
+
- `fidelity_2fa_capture.flow` — TOTP 2FA, option chain, alerts, search, news auth
|
|
21
|
+
- `fidelity_websocket_capture.flow` — MDDS WebSocket, holiday calendar, price triggers, staged orders
|
|
22
|
+
- `fidelity_ts_l2_capture.flow` — Time & Sales / L2 (limited)
|
|
23
|
+
- `fidelity_trading_capture.flow` — Single-leg options, cancel-replace, session keepalive
|
|
24
|
+
- `fidelity_crud_capture.flow` — Conditional orders, watchlist save, price triggers CRUD, screener, alerts, margin details
|
|
25
|
+
|
|
26
|
+
## Filter Scripts
|
|
27
|
+
|
|
28
|
+
- `~/fidelity_filter.py` — ecaap + /prgw/ endpoints
|
|
29
|
+
- `~/fidelity_portfolio_filter.py` — dpservice + streaming-news
|
|
30
|
+
- `~/fastquote_filter.py` — fastquote + ecawsgateway + accounts + analytics + pico
|
|
31
|
+
- `~/ws_dump.py` / `~/ws_dump_full.py` — WebSocket message extractors
|
|
32
|
+
- `~/extract_fields.py` — MDDS field ID extractor
|
|
33
|
+
|
|
34
|
+
## Known API Hosts
|
|
35
|
+
|
|
36
|
+
| Host | Purpose |
|
|
37
|
+
|------|---------|
|
|
38
|
+
| `dpservice.fidelity.com` | Portfolio, orders, research, watchlists, preferences |
|
|
39
|
+
| `fastquote.fidelity.com` | Option chains, montage, charts (JSONP/XML) |
|
|
40
|
+
| `ecaap.fidelity.com` | Authentication (7-step login + TOTP 2FA) |
|
|
41
|
+
| `digital.fidelity.com` | Login page, security context, session management |
|
|
42
|
+
| `ecawsgateway.fidelity.com` | Alerts (SOAP/XML) |
|
|
43
|
+
| `streaming-news.mds.fidelity.com` | News streaming authorization |
|
|
44
|
+
| `mdds-i-tc.fidelity.com` | Real-time market data WebSocket |
|
|
45
|
+
| `fidelity.apps.livevol.com` | Screener (LiveVol ExecuteScan via SAML) |
|
|
46
|
+
| `fidelity-widgets.financial.com` | SAML auth for screener |
|
|
47
|
+
|
|
48
|
+
## Noise Domains to Ignore
|
|
49
|
+
|
|
50
|
+
spotify, microsoft, google, launchdarkly, online-metrix, cfa.fidelity.com (fingerprinting), prgw/digital/login/logging (telemetry)
|
|
51
|
+
|
|
52
|
+
## Your Job
|
|
53
|
+
|
|
54
|
+
1. Read capture files using the appropriate filter script:
|
|
55
|
+
```bash
|
|
56
|
+
mitmdump -n -r ~/capture_file.flow -s ~/fidelity_filter.py 2>/dev/null
|
|
57
|
+
```
|
|
58
|
+
2. For REST endpoints, extract and document:
|
|
59
|
+
- HTTP method and full URL
|
|
60
|
+
- Required headers (auth, CSRF, AppId, fsreqid)
|
|
61
|
+
- Request body shape (JSON/XML/form-encoded)
|
|
62
|
+
- Response body shape with field types
|
|
63
|
+
- Auth requirements (which cookies are needed)
|
|
64
|
+
3. For WebSocket messages, use `ws_dump.py` to extract frames
|
|
65
|
+
4. Cross-reference against `docs/BACKLOG.md` to identify:
|
|
66
|
+
- New endpoints not in the backlog
|
|
67
|
+
- Backlog items that now have capture data
|
|
68
|
+
5. Check existing SDK modules (`src/fidelity_trader/`) to identify what's already implemented
|
|
69
|
+
|
|
70
|
+
## Output Format
|
|
71
|
+
|
|
72
|
+
Write findings to `docs/captures/YYYY-MM-DD-<feature>.md` with:
|
|
73
|
+
- Endpoint sequence diagram (which calls happen in what order)
|
|
74
|
+
- Request/response JSON examples (sanitized — remove real account numbers)
|
|
75
|
+
- Pydantic model suggestions (field names, types, validators)
|
|
76
|
+
- Implementation notes (quirks, edge cases, required headers)
|
|
77
|
+
- Recommended SDK module name and accessor name
|
|
78
|
+
|
|
79
|
+
Also update `docs/BACKLOG.md` to move items from TODO to CAPTURED.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docker-builder
|
|
3
|
+
description: Creates Docker packaging, docker-compose, environment templates, and CLI setup commands. Use when implementing Phase 5 of the service plan — containerized deployment for Linux self-hosting.
|
|
4
|
+
tools: Read, Write, Edit, Glob, Grep, Bash
|
|
5
|
+
model: inherit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You create the Docker packaging and deployment infrastructure for the Fidelity Trader Service.
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
The service plan is at `docs/SERVICE_PLAN.md`, Phase 5 (Tasks 12-13). The service is a FastAPI application in `service/` that wraps the `fidelity-trader-sdk`. Your job is to package it for self-hosted deployment on Linux via Docker.
|
|
13
|
+
|
|
14
|
+
## Files to Create
|
|
15
|
+
|
|
16
|
+
### `docker/Dockerfile`
|
|
17
|
+
|
|
18
|
+
Multi-stage build:
|
|
19
|
+
|
|
20
|
+
```dockerfile
|
|
21
|
+
# Build stage
|
|
22
|
+
FROM python:3.12-slim AS build
|
|
23
|
+
WORKDIR /app
|
|
24
|
+
COPY pyproject.toml .
|
|
25
|
+
COPY src/ src/
|
|
26
|
+
COPY service/ service/
|
|
27
|
+
RUN pip install --no-cache-dir ".[service]"
|
|
28
|
+
|
|
29
|
+
# Runtime stage
|
|
30
|
+
FROM python:3.12-slim
|
|
31
|
+
COPY --from=build /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
|
32
|
+
COPY --from=build /usr/local/bin/uvicorn /usr/local/bin/
|
|
33
|
+
COPY service/ /app/service/
|
|
34
|
+
WORKDIR /app
|
|
35
|
+
|
|
36
|
+
# Create non-root user
|
|
37
|
+
RUN useradd -m -r ftservice && \
|
|
38
|
+
mkdir -p /app/data && \
|
|
39
|
+
chown -R ftservice:ftservice /app/data
|
|
40
|
+
USER ftservice
|
|
41
|
+
|
|
42
|
+
EXPOSE 8787
|
|
43
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
|
|
44
|
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8787/health')" || exit 1
|
|
45
|
+
|
|
46
|
+
CMD ["python", "-m", "service"]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Key decisions:
|
|
50
|
+
- Multi-stage to minimize image size
|
|
51
|
+
- Non-root user for security
|
|
52
|
+
- Health check using stdlib (no curl needed)
|
|
53
|
+
- `/app/data` volume for SQLite persistence
|
|
54
|
+
- No Redis by default (optional compose service)
|
|
55
|
+
|
|
56
|
+
### `docker/docker-compose.yml`
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
services:
|
|
60
|
+
fidelity-trader:
|
|
61
|
+
build:
|
|
62
|
+
context: ..
|
|
63
|
+
dockerfile: docker/Dockerfile
|
|
64
|
+
ports:
|
|
65
|
+
- "${FTSERVICE_PORT:-8787}:8787"
|
|
66
|
+
volumes:
|
|
67
|
+
- ftservice-data:/app/data
|
|
68
|
+
env_file: .env
|
|
69
|
+
restart: unless-stopped
|
|
70
|
+
logging:
|
|
71
|
+
driver: json-file
|
|
72
|
+
options:
|
|
73
|
+
max-size: "10m"
|
|
74
|
+
max-file: "3"
|
|
75
|
+
|
|
76
|
+
volumes:
|
|
77
|
+
ftservice-data:
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `docker/.env.example`
|
|
81
|
+
|
|
82
|
+
```env
|
|
83
|
+
# Fidelity Trader Service Configuration
|
|
84
|
+
# Copy to .env and fill in values
|
|
85
|
+
|
|
86
|
+
# Network
|
|
87
|
+
FTSERVICE_HOST=0.0.0.0
|
|
88
|
+
FTSERVICE_PORT=8787
|
|
89
|
+
|
|
90
|
+
# Security
|
|
91
|
+
FTSERVICE_ENCRYPTION_KEY= # Generate: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
|
92
|
+
FTSERVICE_API_KEY_REQUIRED=true
|
|
93
|
+
|
|
94
|
+
# Session Management
|
|
95
|
+
FTSERVICE_AUTO_REAUTH=true
|
|
96
|
+
FTSERVICE_SESSION_KEEPALIVE_INTERVAL=300
|
|
97
|
+
|
|
98
|
+
# Storage
|
|
99
|
+
FTSERVICE_DB_PATH=data/ftservice.db
|
|
100
|
+
|
|
101
|
+
# Logging
|
|
102
|
+
FTSERVICE_LOG_LEVEL=INFO
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `service/cli.py` — Setup CLI
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
"""CLI for first-time setup and management."""
|
|
109
|
+
|
|
110
|
+
import argparse
|
|
111
|
+
import secrets
|
|
112
|
+
import sys
|
|
113
|
+
from pathlib import Path
|
|
114
|
+
|
|
115
|
+
def setup():
|
|
116
|
+
"""Interactive first-time setup."""
|
|
117
|
+
print("Fidelity Trader Service — Setup")
|
|
118
|
+
print("=" * 40)
|
|
119
|
+
|
|
120
|
+
# 1. Generate encryption key
|
|
121
|
+
from cryptography.fernet import Fernet
|
|
122
|
+
encryption_key = Fernet.generate_key().decode()
|
|
123
|
+
print(f"\nEncryption key: {encryption_key}")
|
|
124
|
+
|
|
125
|
+
# 2. Generate API key
|
|
126
|
+
api_key = secrets.token_urlsafe(32)
|
|
127
|
+
print(f"API key: {api_key}")
|
|
128
|
+
|
|
129
|
+
# 3. Create data directory
|
|
130
|
+
data_dir = Path("data")
|
|
131
|
+
data_dir.mkdir(exist_ok=True)
|
|
132
|
+
print(f"\nData directory: {data_dir.absolute()}")
|
|
133
|
+
|
|
134
|
+
# 4. Write .env file
|
|
135
|
+
env_path = Path(".env")
|
|
136
|
+
if env_path.exists():
|
|
137
|
+
overwrite = input("\n.env already exists. Overwrite? [y/N] ")
|
|
138
|
+
if overwrite.lower() != "y":
|
|
139
|
+
print("Keeping existing .env")
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
env_content = f"""FTSERVICE_HOST=127.0.0.1
|
|
143
|
+
FTSERVICE_PORT=8787
|
|
144
|
+
FTSERVICE_ENCRYPTION_KEY={encryption_key}
|
|
145
|
+
FTSERVICE_API_KEY_REQUIRED=true
|
|
146
|
+
FTSERVICE_AUTO_REAUTH=true
|
|
147
|
+
FTSERVICE_SESSION_KEEPALIVE_INTERVAL=300
|
|
148
|
+
FTSERVICE_DB_PATH=data/ftservice.db
|
|
149
|
+
FTSERVICE_LOG_LEVEL=INFO
|
|
150
|
+
"""
|
|
151
|
+
env_path.write_text(env_content)
|
|
152
|
+
print(f"Wrote {env_path}")
|
|
153
|
+
|
|
154
|
+
print("\n" + "=" * 40)
|
|
155
|
+
print("Setup complete!")
|
|
156
|
+
print(f"\nStart the service:")
|
|
157
|
+
print(f" python -m service")
|
|
158
|
+
print(f"\nOr with Docker:")
|
|
159
|
+
print(f" docker compose -f docker/docker-compose.yml up -d")
|
|
160
|
+
print(f"\nAPI key (save this): {api_key}")
|
|
161
|
+
print(f"Use as: Authorization: Bearer {api_key}")
|
|
162
|
+
|
|
163
|
+
def main():
|
|
164
|
+
parser = argparse.ArgumentParser(prog="ftservice")
|
|
165
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
166
|
+
subparsers.add_parser("setup", help="First-time setup")
|
|
167
|
+
subparsers.add_parser("generate-key", help="Generate new API key")
|
|
168
|
+
|
|
169
|
+
args = parser.parse_args()
|
|
170
|
+
if args.command == "setup":
|
|
171
|
+
setup()
|
|
172
|
+
elif args.command == "generate-key":
|
|
173
|
+
print(secrets.token_urlsafe(32))
|
|
174
|
+
else:
|
|
175
|
+
parser.print_help()
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__":
|
|
178
|
+
main()
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### `.dockerignore`
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
.git
|
|
185
|
+
.github
|
|
186
|
+
__pycache__
|
|
187
|
+
*.pyc
|
|
188
|
+
*.pyo
|
|
189
|
+
.pytest_cache
|
|
190
|
+
.ruff_cache
|
|
191
|
+
tests/
|
|
192
|
+
docs/
|
|
193
|
+
examples/
|
|
194
|
+
*.flow
|
|
195
|
+
*.md
|
|
196
|
+
!README.md
|
|
197
|
+
.env
|
|
198
|
+
data/
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Additional Considerations
|
|
202
|
+
|
|
203
|
+
### Security
|
|
204
|
+
- Non-root container user
|
|
205
|
+
- No secrets in image layers (env vars at runtime)
|
|
206
|
+
- `.env` file never committed (add to `.gitignore`)
|
|
207
|
+
- HTTPS termination expected to be handled by reverse proxy (nginx, Caddy, Traefik)
|
|
208
|
+
|
|
209
|
+
### Persistence
|
|
210
|
+
- SQLite file stored in named Docker volume
|
|
211
|
+
- Volume survives container restarts/upgrades
|
|
212
|
+
- Backup: `docker cp <container>:/app/data/ftservice.db ./backup.db`
|
|
213
|
+
|
|
214
|
+
### Production Notes
|
|
215
|
+
- Add to README: recommend Caddy or nginx for TLS termination
|
|
216
|
+
- Log rotation via Docker's json-file driver
|
|
217
|
+
- Health check for orchestrator integration (Kubernetes, systemd)
|
|
218
|
+
|
|
219
|
+
## Update pyproject.toml
|
|
220
|
+
|
|
221
|
+
Add a CLI entry point:
|
|
222
|
+
```toml
|
|
223
|
+
[project.scripts]
|
|
224
|
+
ftservice = "service.cli:main"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Verification
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Build image
|
|
231
|
+
docker build -f docker/Dockerfile -t fidelity-trader-service .
|
|
232
|
+
|
|
233
|
+
# Test health check
|
|
234
|
+
docker run -d --name ft-test -p 8787:8787 fidelity-trader-service
|
|
235
|
+
curl http://localhost:8787/health
|
|
236
|
+
docker stop ft-test && docker rm ft-test
|
|
237
|
+
```
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: route-builder
|
|
3
|
+
description: Creates FastAPI route files that wrap SDK modules as REST endpoints. Use when building Phase 2 service routes — give it an SDK module name and it generates the route file with proper dependency injection, request models, response wrapping, and tests.
|
|
4
|
+
tools: Read, Write, Edit, Glob, Grep, Bash
|
|
5
|
+
model: inherit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You create FastAPI route files that wrap `fidelity-trader-sdk` modules as REST endpoints.
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
The Fidelity Trader Service exposes all 31 SDK modules as REST endpoints. Each route file follows the same pattern: receive a request, get the authenticated `FidelityClient` via dependency injection, call the SDK method, wrap the result in the standard response envelope. You create these route files and their tests.
|
|
13
|
+
|
|
14
|
+
## The Pattern
|
|
15
|
+
|
|
16
|
+
Every route file follows this structure:
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
# service/routes/<name>.py
|
|
20
|
+
from fastapi import APIRouter, Depends
|
|
21
|
+
from fidelity_trader import FidelityClient
|
|
22
|
+
from service.dependencies import get_session
|
|
23
|
+
from service.models.responses import APIResponse
|
|
24
|
+
|
|
25
|
+
router = APIRouter(prefix="/<name>", tags=["<Name>"])
|
|
26
|
+
|
|
27
|
+
@router.get("/{acct}/positions")
|
|
28
|
+
async def get_positions(
|
|
29
|
+
acct: str,
|
|
30
|
+
client: FidelityClient = Depends(get_session),
|
|
31
|
+
) -> APIResponse:
|
|
32
|
+
import asyncio
|
|
33
|
+
result = await asyncio.to_thread(client.positions.get_positions, [acct])
|
|
34
|
+
return APIResponse(ok=True, data=result.model_dump(by_alias=True))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## SDK Module → Route Mapping
|
|
38
|
+
|
|
39
|
+
Read the SERVICE_PLAN.md at `docs/SERVICE_PLAN.md` for the full endpoint design. Key mappings:
|
|
40
|
+
|
|
41
|
+
### Auth Routes (`service/routes/auth.py`)
|
|
42
|
+
```
|
|
43
|
+
POST /auth/login → SessionManager.login()
|
|
44
|
+
POST /auth/login/totp → SessionManager.submit_totp()
|
|
45
|
+
POST /auth/logout → SessionManager.logout()
|
|
46
|
+
GET /auth/status → SessionManager.status()
|
|
47
|
+
POST /auth/credentials → Store.save_credentials()
|
|
48
|
+
DELETE /auth/credentials → Store.delete_credentials()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Account & Portfolio Routes (`service/routes/accounts.py`)
|
|
52
|
+
SDK accessors: `positions`, `balances`, `transactions`, `option_summary`, `closed_positions`, `loaned_securities`, `tax_lots`, `accounts`
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
GET /accounts → client.accounts.get_accounts()
|
|
56
|
+
GET /accounts/{acct}/positions → client.positions.get_positions([acct])
|
|
57
|
+
GET /accounts/{acct}/balances → client.balances.get_balances([acct])
|
|
58
|
+
GET /accounts/{acct}/transactions → client.transactions.get_transactions(...)
|
|
59
|
+
GET /accounts/{acct}/options-summary → client.option_summary.get_option_summary([acct])
|
|
60
|
+
GET /accounts/{acct}/closed-positions → client.closed_positions.get_closed_positions([acct])
|
|
61
|
+
GET /accounts/{acct}/loaned-securities → client.loaned_securities.get_rates([acct])
|
|
62
|
+
GET /accounts/{acct}/tax-lots/{symbol} → client.tax_lots.get_tax_lots(acct, symbol)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Order Routes (`service/routes/orders.py`)
|
|
66
|
+
SDK accessors: `equity_orders`, `single_option_orders`, `option_orders`, `cancel_order`, `cancel_replace`, `conditional_orders`, `staged_orders`, `order_status`
|
|
67
|
+
|
|
68
|
+
The service translates human-readable values to Fidelity codes:
|
|
69
|
+
- `"buy"` → `"B"`, `"sell"` → `"S"`
|
|
70
|
+
- `"limit"` → `"L"`, `"market"` → `"M"`, `"stop"` → `"S"`
|
|
71
|
+
- `"day"` → `"D"`, `"gtc"` → `"G"`
|
|
72
|
+
|
|
73
|
+
Create request models in `service/models/requests.py` with these human-readable fields.
|
|
74
|
+
|
|
75
|
+
### Market Data Routes (`service/routes/market_data.py`)
|
|
76
|
+
SDK accessors: `option_chain`, `chart`, `available_markets`
|
|
77
|
+
|
|
78
|
+
### Research Routes (`service/routes/research.py`)
|
|
79
|
+
SDK accessors: `research`, `search`, `option_analytics`, `screener`
|
|
80
|
+
|
|
81
|
+
### Watchlists & Alerts Routes (`service/routes/watchlists.py`)
|
|
82
|
+
SDK accessors: `watchlists`, `alerts`, `price_triggers`
|
|
83
|
+
|
|
84
|
+
### Preferences Routes (`service/routes/preferences.py`)
|
|
85
|
+
SDK accessors: `preferences`
|
|
86
|
+
|
|
87
|
+
### Streaming Routes (`service/routes/streaming.py`)
|
|
88
|
+
Handled by the streaming-builder agent — skip this.
|
|
89
|
+
|
|
90
|
+
### Service Routes (`service/routes/service.py`)
|
|
91
|
+
```
|
|
92
|
+
GET /health → {"ok": true, "data": {"status": "healthy"}}
|
|
93
|
+
GET /service/info → version, SDK version, uptime
|
|
94
|
+
POST /service/api-key → generate new API key
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Request Model Pattern
|
|
98
|
+
|
|
99
|
+
For endpoints that need request bodies (orders, analytics):
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
# service/models/requests.py
|
|
103
|
+
from pydantic import BaseModel
|
|
104
|
+
from enum import Enum
|
|
105
|
+
|
|
106
|
+
class OrderAction(str, Enum):
|
|
107
|
+
BUY = "buy"
|
|
108
|
+
SELL = "sell"
|
|
109
|
+
|
|
110
|
+
class OrderType(str, Enum):
|
|
111
|
+
MARKET = "market"
|
|
112
|
+
LIMIT = "limit"
|
|
113
|
+
STOP = "stop"
|
|
114
|
+
STOP_LIMIT = "stop_limit"
|
|
115
|
+
|
|
116
|
+
class EquityOrderRequest(BaseModel):
|
|
117
|
+
account: str
|
|
118
|
+
symbol: str
|
|
119
|
+
action: OrderAction
|
|
120
|
+
quantity: int
|
|
121
|
+
order_type: OrderType = OrderType.MARKET
|
|
122
|
+
limit_price: float | None = None
|
|
123
|
+
stop_price: float | None = None
|
|
124
|
+
time_in_force: str = "day"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Map these to SDK models internally — the service consumer never sees Fidelity's internal codes.
|
|
128
|
+
|
|
129
|
+
## Test Pattern
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# tests/test_service_<name>.py
|
|
133
|
+
import pytest
|
|
134
|
+
from httpx import AsyncClient, ASGITransport
|
|
135
|
+
from unittest.mock import patch, MagicMock
|
|
136
|
+
from service.app import create_app
|
|
137
|
+
|
|
138
|
+
@pytest.fixture
|
|
139
|
+
async def client():
|
|
140
|
+
app = create_app()
|
|
141
|
+
transport = ASGITransport(app=app)
|
|
142
|
+
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
143
|
+
yield ac
|
|
144
|
+
|
|
145
|
+
@pytest.mark.anyio
|
|
146
|
+
async def test_get_positions(client):
|
|
147
|
+
mock_result = MagicMock()
|
|
148
|
+
mock_result.model_dump.return_value = {"positions": [...]}
|
|
149
|
+
|
|
150
|
+
with patch("service.routes.accounts.get_session") as mock_session:
|
|
151
|
+
mock_client = MagicMock()
|
|
152
|
+
mock_client.positions.get_positions.return_value = mock_result
|
|
153
|
+
mock_session.return_value = mock_client
|
|
154
|
+
|
|
155
|
+
resp = await client.get("/accounts/Z12345678/positions")
|
|
156
|
+
assert resp.status_code == 200
|
|
157
|
+
data = resp.json()
|
|
158
|
+
assert data["ok"] is True
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Rules
|
|
162
|
+
|
|
163
|
+
- ONE route file per logical group (accounts, orders, market_data, etc.) — not per SDK module
|
|
164
|
+
- Use `asyncio.to_thread()` for ALL sync SDK calls
|
|
165
|
+
- Always return `APIResponse` envelope
|
|
166
|
+
- Use `Depends(get_session)` for authenticated routes
|
|
167
|
+
- Add proper OpenAPI tags for documentation
|
|
168
|
+
- Query params for simple filters, request body for complex inputs
|
|
169
|
+
- Test each endpoint with mocked SDK calls
|
|
170
|
+
|
|
171
|
+
## How to Use This Agent
|
|
172
|
+
|
|
173
|
+
Invoke with: "Build the accounts routes" or "Create routes for orders"
|
|
174
|
+
|
|
175
|
+
1. Read the target SDK modules to understand available methods and their signatures
|
|
176
|
+
2. Read existing route files (if any) to maintain consistency
|
|
177
|
+
3. Create the route file following the pattern above
|
|
178
|
+
4. Create request models if needed
|
|
179
|
+
5. Register the router in `service/app.py`
|
|
180
|
+
6. Create tests
|
|
181
|
+
7. Run tests: `python -m pytest tests/test_service_<name>.py -v`
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sdk-implementer
|
|
3
|
+
description: Implements SDK API modules from capture analysis docs. Use when a capture has been analyzed and an endpoint needs to be built into the SDK with models, API class, client integration, and tests.
|
|
4
|
+
tools: Read, Write, Edit, Glob, Grep, Bash
|
|
5
|
+
model: inherit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You implement new API modules for the `fidelity-trader-sdk` from capture analysis documents.
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
This SDK wraps Fidelity Trader+ desktop API endpoints as a Python library using `httpx` (sync) and `pydantic v2`. All 31 current modules follow the same pattern. Your job is to implement new modules following established conventions exactly.
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
FidelityClient (client.py)
|
|
18
|
+
├── _http: httpx.Client ← shared cookie jar, ATP_HEADERS
|
|
19
|
+
├── _auth: AuthSession ← 7-step login + CSRF
|
|
20
|
+
├── 31 API modules ← each receives _http in constructor
|
|
21
|
+
└── close()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
All modules share one `httpx.Client`. Cookies propagate automatically for auth.
|
|
25
|
+
|
|
26
|
+
## Implementation Checklist
|
|
27
|
+
|
|
28
|
+
For each new endpoint, create these files:
|
|
29
|
+
|
|
30
|
+
### 1. Pydantic Models (`src/fidelity_trader/models/<name>.py`)
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
34
|
+
from fidelity_trader.models._parsers import _parse_float, _parse_int
|
|
35
|
+
|
|
36
|
+
class ExampleDetail(BaseModel):
|
|
37
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
38
|
+
|
|
39
|
+
symbol: str = Field(alias="symbolId")
|
|
40
|
+
quantity: float = Field(alias="qty")
|
|
41
|
+
|
|
42
|
+
@field_validator("quantity", mode="before")
|
|
43
|
+
@classmethod
|
|
44
|
+
def _coerce_quantity(cls, v):
|
|
45
|
+
return _parse_float(v)
|
|
46
|
+
|
|
47
|
+
class ExampleResponse(BaseModel):
|
|
48
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
49
|
+
|
|
50
|
+
details: list[ExampleDetail] = Field(default_factory=list)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_api_response(cls, data: dict) -> "ExampleResponse":
|
|
54
|
+
# Flatten Fidelity's nested response structure
|
|
55
|
+
...
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Rules:
|
|
59
|
+
- `populate_by_name=True` on every model
|
|
60
|
+
- camelCase `Field(alias=...)` matching the exact API field names
|
|
61
|
+
- `_parse_float` / `_parse_int` for all numeric fields that come as strings
|
|
62
|
+
- `from_api_response()` classmethod to flatten nested JSON
|
|
63
|
+
- Default factories for optional lists/dicts
|
|
64
|
+
|
|
65
|
+
### 2. API Module (`src/fidelity_trader/<package>/<name>.py`)
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import httpx
|
|
69
|
+
from fidelity_trader._http import DPSERVICE_URL, make_req_id
|
|
70
|
+
from fidelity_trader.models.<name> import ExampleResponse
|
|
71
|
+
|
|
72
|
+
class ExampleAPI:
|
|
73
|
+
def __init__(self, http: httpx.Client) -> None:
|
|
74
|
+
self._http = http
|
|
75
|
+
|
|
76
|
+
def get_example(self, account_ids: list[str]) -> ExampleResponse:
|
|
77
|
+
body = {
|
|
78
|
+
"getExample": {
|
|
79
|
+
"request": {
|
|
80
|
+
"accountIds": account_ids,
|
|
81
|
+
},
|
|
82
|
+
"requestHeader": {"reqId": make_req_id()},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
resp = self._http.post(
|
|
86
|
+
f"{DPSERVICE_URL}/ftgw/dp/...",
|
|
87
|
+
json=body,
|
|
88
|
+
)
|
|
89
|
+
resp.raise_for_status()
|
|
90
|
+
return ExampleResponse.from_api_response(resp.json())
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Rules:
|
|
94
|
+
- Constructor takes only `http: httpx.Client`
|
|
95
|
+
- Use `make_req_id()` for all requests
|
|
96
|
+
- Use the correct base URL constant from `_http.py`
|
|
97
|
+
- `raise_for_status()` after every request
|
|
98
|
+
- Return parsed Pydantic models, not raw dicts
|
|
99
|
+
|
|
100
|
+
### 3. Client Integration (`src/fidelity_trader/client.py`)
|
|
101
|
+
|
|
102
|
+
Add import and accessor:
|
|
103
|
+
```python
|
|
104
|
+
from fidelity_trader.<package>.<name> import ExampleAPI
|
|
105
|
+
# ...
|
|
106
|
+
self.example = ExampleAPI(self._http)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 4. Tests (`tests/test_<name>.py`)
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import httpx
|
|
113
|
+
import pytest
|
|
114
|
+
import respx
|
|
115
|
+
from fidelity_trader.<package>.<name> import ExampleAPI
|
|
116
|
+
from fidelity_trader._http import DPSERVICE_URL
|
|
117
|
+
|
|
118
|
+
@pytest.fixture
|
|
119
|
+
def api():
|
|
120
|
+
client = httpx.Client()
|
|
121
|
+
yield ExampleAPI(client)
|
|
122
|
+
client.close()
|
|
123
|
+
|
|
124
|
+
@respx.mock
|
|
125
|
+
def test_get_example(api):
|
|
126
|
+
mock_response = {...} # From capture data
|
|
127
|
+
respx.post(f"{DPSERVICE_URL}/ftgw/dp/...").mock(
|
|
128
|
+
return_value=httpx.Response(200, json=mock_response)
|
|
129
|
+
)
|
|
130
|
+
result = api.get_example(["Z12345678"])
|
|
131
|
+
assert isinstance(result, ExampleResponse)
|
|
132
|
+
...
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Rules:
|
|
136
|
+
- Use `respx` for HTTP mocking (never hit real APIs)
|
|
137
|
+
- Test model parsing (field values, type coercion, nested objects)
|
|
138
|
+
- Test error cases (empty responses, missing fields)
|
|
139
|
+
- Test `from_api_response()` separately with raw JSON fixtures
|
|
140
|
+
- Run `pytest tests/test_<name>.py -v` after writing tests
|
|
141
|
+
|
|
142
|
+
### 5. Update `test_client.py`
|
|
143
|
+
|
|
144
|
+
Add the new module to:
|
|
145
|
+
- `test_client_has_all_module_attributes` — assert isinstance
|
|
146
|
+
- `test_all_modules_share_same_http_client` — assert `._http is http`
|
|
147
|
+
|
|
148
|
+
### 6. Update `__init__.py` exports if the module has public classes
|
|
149
|
+
|
|
150
|
+
## API Quirks to Remember
|
|
151
|
+
|
|
152
|
+
- Single-leg option place: `previewInd: false, confInd: false`
|
|
153
|
+
- Multi-leg option: `previewInd: true, confInd: true`
|
|
154
|
+
- Conditional orders: top-level key is `parameters` (plural), NOT `request.parameter`
|
|
155
|
+
- Cancel-replace: `orderNumOrig` at parameter level, `confNum` in `baseOrderDetail`
|
|
156
|
+
- Error responses: HTTP 200 with `respTypeCode: "E"` and `orderConfirmMsgs`
|
|
157
|
+
- Fastquote: JSONP-wrapped XML responses
|
|
158
|
+
- Alerts: SOAP/XML on ecawsgateway
|
|
159
|
+
- Screener: SAML auth + XML results from LiveVol
|
|
160
|
+
|
|
161
|
+
## Verification
|
|
162
|
+
|
|
163
|
+
After implementation:
|
|
164
|
+
```bash
|
|
165
|
+
cd ~/fidelity-trader-sdk && python -m pytest tests/ -v --tb=short
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
All tests must pass. Report the final test count.
|