tradeodds 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.
- tradeodds-0.1.0/.gitignore +72 -0
- tradeodds-0.1.0/LICENSE +21 -0
- tradeodds-0.1.0/PKG-INFO +187 -0
- tradeodds-0.1.0/README.md +136 -0
- tradeodds-0.1.0/examples/quickstart.py +43 -0
- tradeodds-0.1.0/pyproject.toml +53 -0
- tradeodds-0.1.0/tradeodds/__init__.py +62 -0
- tradeodds-0.1.0/tradeodds/client.py +268 -0
- tradeodds-0.1.0/tradeodds/exceptions.py +72 -0
- tradeodds-0.1.0/tradeodds/types.py +112 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
dist-ssr/
|
|
7
|
+
|
|
8
|
+
# Environment variables (NEVER commit)
|
|
9
|
+
.env
|
|
10
|
+
.env.local
|
|
11
|
+
.env.*.local
|
|
12
|
+
backend/.env
|
|
13
|
+
|
|
14
|
+
# Python
|
|
15
|
+
__pycache__/
|
|
16
|
+
*.pyc
|
|
17
|
+
*.pyo
|
|
18
|
+
backend/venv/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
|
|
21
|
+
# OS files
|
|
22
|
+
.DS_Store
|
|
23
|
+
Thumbs.db
|
|
24
|
+
|
|
25
|
+
# IDE
|
|
26
|
+
.vscode/
|
|
27
|
+
.idea/
|
|
28
|
+
.claude/
|
|
29
|
+
*.swp
|
|
30
|
+
*.swo
|
|
31
|
+
|
|
32
|
+
# Logs
|
|
33
|
+
*.log
|
|
34
|
+
npm-debug.log*
|
|
35
|
+
|
|
36
|
+
# Lock files (using npm)
|
|
37
|
+
bun.lockb
|
|
38
|
+
|
|
39
|
+
# Content engine output
|
|
40
|
+
backend/scripts/output/
|
|
41
|
+
|
|
42
|
+
# GuessTheMove dedup ledger (local state)
|
|
43
|
+
video/props/.guess_ledger.json
|
|
44
|
+
|
|
45
|
+
# YouTube OAuth credentials (NEVER commit)
|
|
46
|
+
backend/scripts/client_secret.json
|
|
47
|
+
backend/scripts/youtube_token.json
|
|
48
|
+
video/props/.youtube_uploads.json
|
|
49
|
+
|
|
50
|
+
# TikTok OAuth credentials (NEVER commit)
|
|
51
|
+
backend/scripts/tiktok_token.json
|
|
52
|
+
video/props/.tiktok_uploads.json
|
|
53
|
+
|
|
54
|
+
# Agent reports (ephemeral, regenerated nightly)
|
|
55
|
+
backend/scripts/agents/reports/*.json
|
|
56
|
+
|
|
57
|
+
# Astro content hub (generated at build)
|
|
58
|
+
content/.astro/
|
|
59
|
+
content/dist/
|
|
60
|
+
|
|
61
|
+
# Misc
|
|
62
|
+
*.tsbuildinfo
|
|
63
|
+
|
|
64
|
+
# Claude dev handoff docs (internal session prompts, not production code)
|
|
65
|
+
Context/HANDOFF_POST_MIGRATION.md
|
|
66
|
+
Context/HANDOFF_TRADEODDS_MIGRATION.md
|
|
67
|
+
Context/PROMPT_PHASE_11A.md
|
|
68
|
+
Context/SESSION_PROMPT_RECOMMENDATIONS.md
|
|
69
|
+
Context/RECOMMENDATIONS.md
|
|
70
|
+
.vercel
|
|
71
|
+
.env*.local
|
|
72
|
+
.mcp.json
|
tradeodds-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TradeOdds
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tradeodds-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tradeodds
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the TradeOdds REST API — quantitative pattern analysis on ~3,200 symbols.
|
|
5
|
+
Project-URL: Homepage, https://tradeodds.io
|
|
6
|
+
Project-URL: Documentation, https://tradeodds.io/api-docs
|
|
7
|
+
Project-URL: Repository, https://github.com/cpoly/tradeodds
|
|
8
|
+
Project-URL: Issues, https://github.com/cpoly/tradeodds/issues
|
|
9
|
+
Author-email: TradeOdds <support@tradeodds.io>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 TradeOdds
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: api,factor-match,finance,quantitative,sdk,tradeodds,trading
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
44
|
+
Requires-Python: >=3.9
|
|
45
|
+
Requires-Dist: requests>=2.28.0
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
48
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
49
|
+
Requires-Dist: types-requests; extra == 'dev'
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
# tradeodds — Python SDK
|
|
53
|
+
|
|
54
|
+
Official Python client for the [TradeOdds](https://tradeodds.io) REST API. Run quantitative pattern analysis on ~3,200 symbols (US equities, ETFs, major crypto) with one function call.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install tradeodds
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quickstart
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from tradeodds import TradeOddsClient
|
|
64
|
+
|
|
65
|
+
client = TradeOddsClient() # reads TRADEODDS_API_KEY from env
|
|
66
|
+
|
|
67
|
+
result = client.analyze(
|
|
68
|
+
symbol="SPY",
|
|
69
|
+
forward_period="5d",
|
|
70
|
+
conditions={"daily_change": True, "vix_level": True, "regime": True},
|
|
71
|
+
lookback_years="20y",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
stats = result["forward_stats"]
|
|
75
|
+
print(f"{result['match_count']} matches | win rate {stats['win_rate']:.0%} | median {stats['median_return']:+.2%}")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Get an API key at <https://tradeodds.io/account>. Free tier ships with the platform — pay-as-you-go pricing kicks in at $0.05/analyze and $0.15/factor-match.
|
|
79
|
+
|
|
80
|
+
## What's in the box
|
|
81
|
+
|
|
82
|
+
| Method | Endpoint | Auth | Cost |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| `client.symbols()` | `GET /api/v1/symbols` | none | free |
|
|
85
|
+
| `client.analyze(symbol, ...)` | `POST /api/v1/analyze` | API key | $0.05 |
|
|
86
|
+
| `client.factor_match(...)` | `POST /api/v1/factor-match` | API key | $0.15 |
|
|
87
|
+
|
|
88
|
+
## Installation
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install tradeodds
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Requires Python 3.9+. The only runtime dependency is [`requests`](https://requests.readthedocs.io).
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
| Source | Variable | Notes |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| Env | `TRADEODDS_API_KEY` | `sk-to-...` token. Default auth source. |
|
|
101
|
+
| Env | `TRADEODDS_BASE_URL` | Override for staging / self-host. Defaults to production. |
|
|
102
|
+
| Constructor | `TradeOddsClient(api_key=..., base_url=..., timeout=200.0)` | Explicit overrides win over env. |
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from tradeodds import TradeOddsClient
|
|
106
|
+
|
|
107
|
+
client = TradeOddsClient(api_key="sk-to-abc123...", timeout=60.0)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## API reference
|
|
111
|
+
|
|
112
|
+
### `client.symbols(active_only=True)`
|
|
113
|
+
|
|
114
|
+
List every symbol available for analysis.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
universe = client.symbols()
|
|
118
|
+
print(f"{universe['count']} symbols")
|
|
119
|
+
spy = next(s for s in universe["symbols"] if s["symbol"] == "SPY")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `client.analyze(symbol, **kwargs)`
|
|
123
|
+
|
|
124
|
+
Returns probability-weighted historical analogs for the symbol's current DNA fingerprint. Auth required. $0.05/call.
|
|
125
|
+
|
|
126
|
+
| Arg | Type | Default | Notes |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| `symbol` | str | — | Ticker (case-insensitive). |
|
|
129
|
+
| `reference_period` | `1d` `5d` `1m` … | `1d` | How recent the observed window is. |
|
|
130
|
+
| `forward_period` | `1d` `5d` `20d` … | `5d` | Trading days forward to compute outcomes. |
|
|
131
|
+
| `conditions` | `DNAConditions` | `{daily_change: True}` | Toggle factors: `vix_level`, `regime`, `rsi_zone`, `streak`, `macro_risk`, etc. |
|
|
132
|
+
| `lookback_years` | `1y` `5y` `20y` `max` | `max` | History window for matches. |
|
|
133
|
+
| `price_tolerance` | int 0-3 | 0 | Bucket-step tolerance for price. |
|
|
134
|
+
| `vix_tolerance` | int 0-3 | 0 | Bucket-step tolerance for VIX. |
|
|
135
|
+
|
|
136
|
+
### `client.factor_match(**kwargs)`
|
|
137
|
+
|
|
138
|
+
Scans all symbols for whose current state matches the supplied conditions. Returns a ranked list with historical forward-return stats. Auth required. $0.15/call.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
result = client.factor_match(
|
|
142
|
+
conditions={"vix_level": True, "regime": True, "rsi_zone": True},
|
|
143
|
+
filters={"is_etf": False, "price_min": 10},
|
|
144
|
+
perf_filter={"metric": "win_5d", "operator": "gt", "threshold": 0.6},
|
|
145
|
+
min_instances=20,
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Error handling
|
|
150
|
+
|
|
151
|
+
Errors are typed exceptions that preserve the structured envelope (`code`, `hint`, `request_id`):
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from tradeodds import ApiError, AuthError, NotFoundError, RateLimitError
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
result = client.analyze(symbol="SPY")
|
|
158
|
+
except RateLimitError as exc:
|
|
159
|
+
print(exc.code, exc.message, exc.hint, exc.request_id)
|
|
160
|
+
except AuthError:
|
|
161
|
+
print("Set TRADEODDS_API_KEY")
|
|
162
|
+
except NotFoundError:
|
|
163
|
+
print("Unknown symbol")
|
|
164
|
+
except ApiError as exc:
|
|
165
|
+
print(f"[{exc.status_code}] {exc.code}: {exc.message}")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
| Exception | When |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `AuthError` | 401 — missing or invalid key |
|
|
171
|
+
| `NotFoundError` | 404 — unknown symbol |
|
|
172
|
+
| `ValidationError` | 400 / 422 — bad request body |
|
|
173
|
+
| `RateLimitError` | 429 — daily or per-minute cap |
|
|
174
|
+
| `ServerError` | 5xx and client-side timeouts (504 from server-side timeout) |
|
|
175
|
+
| `ApiError` | base class for all of the above |
|
|
176
|
+
|
|
177
|
+
Every exception exposes:
|
|
178
|
+
|
|
179
|
+
- `exc.code` — machine-readable code (e.g. `rate_limit_exceeded`)
|
|
180
|
+
- `exc.message` — human-readable summary
|
|
181
|
+
- `exc.hint` — suggested remediation, when the API provides one
|
|
182
|
+
- `exc.request_id` — server request id for support contact
|
|
183
|
+
- `exc.status_code` — HTTP status
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# tradeodds — Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python client for the [TradeOdds](https://tradeodds.io) REST API. Run quantitative pattern analysis on ~3,200 symbols (US equities, ETFs, major crypto) with one function call.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install tradeodds
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from tradeodds import TradeOddsClient
|
|
13
|
+
|
|
14
|
+
client = TradeOddsClient() # reads TRADEODDS_API_KEY from env
|
|
15
|
+
|
|
16
|
+
result = client.analyze(
|
|
17
|
+
symbol="SPY",
|
|
18
|
+
forward_period="5d",
|
|
19
|
+
conditions={"daily_change": True, "vix_level": True, "regime": True},
|
|
20
|
+
lookback_years="20y",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
stats = result["forward_stats"]
|
|
24
|
+
print(f"{result['match_count']} matches | win rate {stats['win_rate']:.0%} | median {stats['median_return']:+.2%}")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Get an API key at <https://tradeodds.io/account>. Free tier ships with the platform — pay-as-you-go pricing kicks in at $0.05/analyze and $0.15/factor-match.
|
|
28
|
+
|
|
29
|
+
## What's in the box
|
|
30
|
+
|
|
31
|
+
| Method | Endpoint | Auth | Cost |
|
|
32
|
+
|---|---|---|---|
|
|
33
|
+
| `client.symbols()` | `GET /api/v1/symbols` | none | free |
|
|
34
|
+
| `client.analyze(symbol, ...)` | `POST /api/v1/analyze` | API key | $0.05 |
|
|
35
|
+
| `client.factor_match(...)` | `POST /api/v1/factor-match` | API key | $0.15 |
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install tradeodds
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Requires Python 3.9+. The only runtime dependency is [`requests`](https://requests.readthedocs.io).
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
| Source | Variable | Notes |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| Env | `TRADEODDS_API_KEY` | `sk-to-...` token. Default auth source. |
|
|
50
|
+
| Env | `TRADEODDS_BASE_URL` | Override for staging / self-host. Defaults to production. |
|
|
51
|
+
| Constructor | `TradeOddsClient(api_key=..., base_url=..., timeout=200.0)` | Explicit overrides win over env. |
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from tradeodds import TradeOddsClient
|
|
55
|
+
|
|
56
|
+
client = TradeOddsClient(api_key="sk-to-abc123...", timeout=60.0)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## API reference
|
|
60
|
+
|
|
61
|
+
### `client.symbols(active_only=True)`
|
|
62
|
+
|
|
63
|
+
List every symbol available for analysis.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
universe = client.symbols()
|
|
67
|
+
print(f"{universe['count']} symbols")
|
|
68
|
+
spy = next(s for s in universe["symbols"] if s["symbol"] == "SPY")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `client.analyze(symbol, **kwargs)`
|
|
72
|
+
|
|
73
|
+
Returns probability-weighted historical analogs for the symbol's current DNA fingerprint. Auth required. $0.05/call.
|
|
74
|
+
|
|
75
|
+
| Arg | Type | Default | Notes |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| `symbol` | str | — | Ticker (case-insensitive). |
|
|
78
|
+
| `reference_period` | `1d` `5d` `1m` … | `1d` | How recent the observed window is. |
|
|
79
|
+
| `forward_period` | `1d` `5d` `20d` … | `5d` | Trading days forward to compute outcomes. |
|
|
80
|
+
| `conditions` | `DNAConditions` | `{daily_change: True}` | Toggle factors: `vix_level`, `regime`, `rsi_zone`, `streak`, `macro_risk`, etc. |
|
|
81
|
+
| `lookback_years` | `1y` `5y` `20y` `max` | `max` | History window for matches. |
|
|
82
|
+
| `price_tolerance` | int 0-3 | 0 | Bucket-step tolerance for price. |
|
|
83
|
+
| `vix_tolerance` | int 0-3 | 0 | Bucket-step tolerance for VIX. |
|
|
84
|
+
|
|
85
|
+
### `client.factor_match(**kwargs)`
|
|
86
|
+
|
|
87
|
+
Scans all symbols for whose current state matches the supplied conditions. Returns a ranked list with historical forward-return stats. Auth required. $0.15/call.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
result = client.factor_match(
|
|
91
|
+
conditions={"vix_level": True, "regime": True, "rsi_zone": True},
|
|
92
|
+
filters={"is_etf": False, "price_min": 10},
|
|
93
|
+
perf_filter={"metric": "win_5d", "operator": "gt", "threshold": 0.6},
|
|
94
|
+
min_instances=20,
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Error handling
|
|
99
|
+
|
|
100
|
+
Errors are typed exceptions that preserve the structured envelope (`code`, `hint`, `request_id`):
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from tradeodds import ApiError, AuthError, NotFoundError, RateLimitError
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
result = client.analyze(symbol="SPY")
|
|
107
|
+
except RateLimitError as exc:
|
|
108
|
+
print(exc.code, exc.message, exc.hint, exc.request_id)
|
|
109
|
+
except AuthError:
|
|
110
|
+
print("Set TRADEODDS_API_KEY")
|
|
111
|
+
except NotFoundError:
|
|
112
|
+
print("Unknown symbol")
|
|
113
|
+
except ApiError as exc:
|
|
114
|
+
print(f"[{exc.status_code}] {exc.code}: {exc.message}")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Exception | When |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `AuthError` | 401 — missing or invalid key |
|
|
120
|
+
| `NotFoundError` | 404 — unknown symbol |
|
|
121
|
+
| `ValidationError` | 400 / 422 — bad request body |
|
|
122
|
+
| `RateLimitError` | 429 — daily or per-minute cap |
|
|
123
|
+
| `ServerError` | 5xx and client-side timeouts (504 from server-side timeout) |
|
|
124
|
+
| `ApiError` | base class for all of the above |
|
|
125
|
+
|
|
126
|
+
Every exception exposes:
|
|
127
|
+
|
|
128
|
+
- `exc.code` — machine-readable code (e.g. `rate_limit_exceeded`)
|
|
129
|
+
- `exc.message` — human-readable summary
|
|
130
|
+
- `exc.hint` — suggested remediation, when the API provides one
|
|
131
|
+
- `exc.request_id` — server request id for support contact
|
|
132
|
+
- `exc.status_code` — HTTP status
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""TradeOdds SDK quickstart.
|
|
2
|
+
|
|
3
|
+
Set TRADEODDS_API_KEY in your environment, then run:
|
|
4
|
+
|
|
5
|
+
python examples/quickstart.py
|
|
6
|
+
|
|
7
|
+
Outputs the historical win rate and median 5-day forward return for SPY when
|
|
8
|
+
VIX is in its current regime — an end-to-end live API call in <30 lines.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from tradeodds import ApiError, TradeOddsClient
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> int:
|
|
20
|
+
if not os.environ.get("TRADEODDS_API_KEY"):
|
|
21
|
+
print("Set TRADEODDS_API_KEY first. Get a key at https://tradeodds.io/account.")
|
|
22
|
+
return 1
|
|
23
|
+
|
|
24
|
+
with TradeOddsClient() as client:
|
|
25
|
+
try:
|
|
26
|
+
result = client.analyze(
|
|
27
|
+
symbol="SPY",
|
|
28
|
+
forward_period="5d",
|
|
29
|
+
conditions={"daily_change": True, "vix_level": True, "regime": True},
|
|
30
|
+
lookback_years="20y",
|
|
31
|
+
)
|
|
32
|
+
except ApiError as exc:
|
|
33
|
+
print(f"API error [{exc.code}]: {exc.message}")
|
|
34
|
+
if exc.hint:
|
|
35
|
+
print(f"hint: {exc.hint}")
|
|
36
|
+
return 1
|
|
37
|
+
|
|
38
|
+
print(json.dumps(result, indent=2, default=str)[:1500])
|
|
39
|
+
return 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
sys.exit(main())
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tradeodds"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the TradeOdds REST API — quantitative pattern analysis on ~3,200 symbols."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "TradeOdds", email = "support@tradeodds.io" }]
|
|
13
|
+
keywords = ["tradeodds", "trading", "quantitative", "finance", "api", "sdk", "factor-match"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"requests>=2.28.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"mypy>=1.0",
|
|
34
|
+
"types-requests",
|
|
35
|
+
"pytest>=7.0",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://tradeodds.io"
|
|
40
|
+
Documentation = "https://tradeodds.io/api-docs"
|
|
41
|
+
Repository = "https://github.com/cpoly/tradeodds"
|
|
42
|
+
Issues = "https://github.com/cpoly/tradeodds/issues"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["tradeodds"]
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.sdist]
|
|
48
|
+
include = [
|
|
49
|
+
"tradeodds/",
|
|
50
|
+
"examples/",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""TradeOdds — official Python SDK.
|
|
2
|
+
|
|
3
|
+
Quickstart:
|
|
4
|
+
|
|
5
|
+
>>> from tradeodds import TradeOddsClient
|
|
6
|
+
>>> client = TradeOddsClient() # reads TRADEODDS_API_KEY
|
|
7
|
+
>>> result = client.analyze(symbol="SPY", conditions={"vix_level": True})
|
|
8
|
+
>>> print(f"{result['match_count']} historical matches")
|
|
9
|
+
"""
|
|
10
|
+
from .client import DEFAULT_BASE_URL, TradeOddsClient, __version__
|
|
11
|
+
from .exceptions import (
|
|
12
|
+
ApiError,
|
|
13
|
+
AuthError,
|
|
14
|
+
NotFoundError,
|
|
15
|
+
RateLimitError,
|
|
16
|
+
ServerError,
|
|
17
|
+
ValidationError,
|
|
18
|
+
)
|
|
19
|
+
from .types import (
|
|
20
|
+
AnalyzeResult,
|
|
21
|
+
DNAConditions,
|
|
22
|
+
FactorMatchResult,
|
|
23
|
+
ForwardPeriod,
|
|
24
|
+
LookbackYears,
|
|
25
|
+
PerfFilter,
|
|
26
|
+
PerfMetric,
|
|
27
|
+
PerfOperator,
|
|
28
|
+
ReferencePeriod,
|
|
29
|
+
ScreenerFilters,
|
|
30
|
+
Symbol,
|
|
31
|
+
SymbolListResponse,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Backwards-compatibility alias — many SDKs expose `Client`.
|
|
35
|
+
Client = TradeOddsClient
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"__version__",
|
|
39
|
+
"Client",
|
|
40
|
+
"TradeOddsClient",
|
|
41
|
+
"DEFAULT_BASE_URL",
|
|
42
|
+
# Exceptions
|
|
43
|
+
"ApiError",
|
|
44
|
+
"AuthError",
|
|
45
|
+
"NotFoundError",
|
|
46
|
+
"RateLimitError",
|
|
47
|
+
"ServerError",
|
|
48
|
+
"ValidationError",
|
|
49
|
+
# Types
|
|
50
|
+
"AnalyzeResult",
|
|
51
|
+
"DNAConditions",
|
|
52
|
+
"FactorMatchResult",
|
|
53
|
+
"ForwardPeriod",
|
|
54
|
+
"LookbackYears",
|
|
55
|
+
"PerfFilter",
|
|
56
|
+
"PerfMetric",
|
|
57
|
+
"PerfOperator",
|
|
58
|
+
"ReferencePeriod",
|
|
59
|
+
"ScreenerFilters",
|
|
60
|
+
"Symbol",
|
|
61
|
+
"SymbolListResponse",
|
|
62
|
+
]
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Synchronous HTTP client for the TradeOdds REST API.
|
|
2
|
+
|
|
3
|
+
Thin wrapper around ``requests``: one method per endpoint, typed inputs,
|
|
4
|
+
typed exceptions on 4xx/5xx with the structured envelope's ``code`` / ``hint``
|
|
5
|
+
/ ``request_id`` preserved as exception attributes.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
from .exceptions import (
|
|
15
|
+
ApiError,
|
|
16
|
+
AuthError,
|
|
17
|
+
NotFoundError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
ServerError,
|
|
20
|
+
ValidationError,
|
|
21
|
+
)
|
|
22
|
+
from .types import (
|
|
23
|
+
AnalyzeResult,
|
|
24
|
+
DNAConditions,
|
|
25
|
+
FactorMatchResult,
|
|
26
|
+
ForwardPeriod,
|
|
27
|
+
LookbackYears,
|
|
28
|
+
PerfFilter,
|
|
29
|
+
ReferencePeriod,
|
|
30
|
+
ScreenerFilters,
|
|
31
|
+
SymbolListResponse,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__version__ = "0.1.0"
|
|
35
|
+
|
|
36
|
+
DEFAULT_BASE_URL = "https://tradeodds-production.up.railway.app"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _exception_for_status(status: int) -> type:
|
|
40
|
+
if status == 401:
|
|
41
|
+
return AuthError
|
|
42
|
+
if status == 404:
|
|
43
|
+
return NotFoundError
|
|
44
|
+
if status == 429:
|
|
45
|
+
return RateLimitError
|
|
46
|
+
if status in (400, 422):
|
|
47
|
+
return ValidationError
|
|
48
|
+
if status >= 500:
|
|
49
|
+
return ServerError
|
|
50
|
+
return ApiError
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TradeOddsClient:
|
|
54
|
+
"""Synchronous client for the TradeOdds public REST API.
|
|
55
|
+
|
|
56
|
+
Reads ``TRADEODDS_API_KEY`` from the environment by default. Override with
|
|
57
|
+
``api_key=`` / ``base_url=`` / ``timeout=`` constructor args. Pass a custom
|
|
58
|
+
``session=requests.Session()`` to share connection pooling or attach
|
|
59
|
+
retries. ``symbols()`` works without an API key;
|
|
60
|
+
``analyze()`` and ``factor_match()`` require one.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
api_key: Optional[str] = None,
|
|
66
|
+
*,
|
|
67
|
+
base_url: Optional[str] = None,
|
|
68
|
+
timeout: float = 200.0,
|
|
69
|
+
session: Optional[requests.Session] = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
self.api_key = api_key or os.environ.get("TRADEODDS_API_KEY")
|
|
72
|
+
self.base_url = (
|
|
73
|
+
base_url
|
|
74
|
+
or os.environ.get("TRADEODDS_BASE_URL")
|
|
75
|
+
or DEFAULT_BASE_URL
|
|
76
|
+
).rstrip("/")
|
|
77
|
+
self.timeout = timeout
|
|
78
|
+
self._session = session or requests.Session()
|
|
79
|
+
|
|
80
|
+
# ── Public methods ──────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
def symbols(self, *, active_only: bool = True) -> SymbolListResponse:
|
|
83
|
+
"""``GET /api/v1/symbols`` — list every available instrument. No auth required."""
|
|
84
|
+
return self._request(
|
|
85
|
+
"GET",
|
|
86
|
+
"/api/v1/symbols",
|
|
87
|
+
params={"active_only": str(active_only).lower()},
|
|
88
|
+
auth_required=False,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def analyze(
|
|
92
|
+
self,
|
|
93
|
+
symbol: str,
|
|
94
|
+
*,
|
|
95
|
+
reference_period: ReferencePeriod = "1d",
|
|
96
|
+
forward_period: ForwardPeriod = "5d",
|
|
97
|
+
conditions: Optional[DNAConditions] = None,
|
|
98
|
+
lookback_years: LookbackYears = "max",
|
|
99
|
+
price_tolerance: int = 0,
|
|
100
|
+
vix_tolerance: int = 0,
|
|
101
|
+
) -> AnalyzeResult:
|
|
102
|
+
"""``POST /api/v1/analyze`` — historical pattern analysis on one symbol.
|
|
103
|
+
|
|
104
|
+
Returns match count, win rate, median forward return, and historical
|
|
105
|
+
instances. Billed at $0.05/call. Daily cap: 1,000/key. See README for
|
|
106
|
+
the full conditions vocabulary.
|
|
107
|
+
"""
|
|
108
|
+
body: Dict[str, Any] = {
|
|
109
|
+
"symbol": symbol,
|
|
110
|
+
"reference_period": reference_period,
|
|
111
|
+
"forward_period": forward_period,
|
|
112
|
+
"lookback_years": lookback_years,
|
|
113
|
+
"price_tolerance": price_tolerance,
|
|
114
|
+
"vix_tolerance": vix_tolerance,
|
|
115
|
+
}
|
|
116
|
+
if conditions is not None:
|
|
117
|
+
body["conditions"] = dict(conditions)
|
|
118
|
+
return self._request("POST", "/api/v1/analyze", json=body, auth_required=True)
|
|
119
|
+
|
|
120
|
+
def factor_match(
|
|
121
|
+
self,
|
|
122
|
+
*,
|
|
123
|
+
forward_periods: Optional[List[ForwardPeriod]] = None,
|
|
124
|
+
conditions: Optional[DNAConditions] = None,
|
|
125
|
+
filters: Optional[ScreenerFilters] = None,
|
|
126
|
+
perf_filter: Optional[PerfFilter] = None,
|
|
127
|
+
history_range: LookbackYears = "max",
|
|
128
|
+
min_instances: int = 10,
|
|
129
|
+
price_tolerance: int = 0,
|
|
130
|
+
vix_tolerance: int = 0,
|
|
131
|
+
) -> FactorMatchResult:
|
|
132
|
+
"""``POST /api/v1/factor-match`` — scan all symbols for current DNA matches.
|
|
133
|
+
|
|
134
|
+
Returns a ranked list with historical forward-return stats per symbol.
|
|
135
|
+
Billed at $0.15/call. Daily cap: 200/key. Server caps at 180s; narrow
|
|
136
|
+
``filters`` if you time out.
|
|
137
|
+
"""
|
|
138
|
+
body: Dict[str, Any] = {
|
|
139
|
+
"forward_periods": forward_periods or ["1d", "5d", "20d"],
|
|
140
|
+
"history_range": history_range,
|
|
141
|
+
"min_instances": min_instances,
|
|
142
|
+
"price_tolerance": price_tolerance,
|
|
143
|
+
"vix_tolerance": vix_tolerance,
|
|
144
|
+
}
|
|
145
|
+
if conditions is not None:
|
|
146
|
+
body["conditions"] = dict(conditions)
|
|
147
|
+
if filters is not None:
|
|
148
|
+
body["filters"] = dict(filters)
|
|
149
|
+
if perf_filter is not None:
|
|
150
|
+
body["perf_filter"] = dict(perf_filter)
|
|
151
|
+
return self._request(
|
|
152
|
+
"POST", "/api/v1/factor-match", json=body, auth_required=True
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# ── Internals ───────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
def _headers(self, auth_required: bool) -> Dict[str, str]:
|
|
158
|
+
headers = {
|
|
159
|
+
"User-Agent": f"tradeodds-python/{__version__}",
|
|
160
|
+
"X-Client": "python-sdk",
|
|
161
|
+
"Accept": "application/json",
|
|
162
|
+
}
|
|
163
|
+
if auth_required or self.api_key:
|
|
164
|
+
if not self.api_key:
|
|
165
|
+
raise AuthError(
|
|
166
|
+
"API key required.",
|
|
167
|
+
code="missing_api_key",
|
|
168
|
+
hint=(
|
|
169
|
+
"Pass api_key=... to TradeOddsClient or set the "
|
|
170
|
+
"TRADEODDS_API_KEY environment variable. "
|
|
171
|
+
"Get a key at https://tradeodds.io/account."
|
|
172
|
+
),
|
|
173
|
+
status_code=401,
|
|
174
|
+
)
|
|
175
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
176
|
+
return headers
|
|
177
|
+
|
|
178
|
+
def _request(
|
|
179
|
+
self,
|
|
180
|
+
method: str,
|
|
181
|
+
path: str,
|
|
182
|
+
*,
|
|
183
|
+
params: Optional[Dict[str, Any]] = None,
|
|
184
|
+
json: Optional[Dict[str, Any]] = None,
|
|
185
|
+
auth_required: bool = False,
|
|
186
|
+
) -> Any:
|
|
187
|
+
url = f"{self.base_url}{path}"
|
|
188
|
+
try:
|
|
189
|
+
resp = self._session.request(
|
|
190
|
+
method,
|
|
191
|
+
url,
|
|
192
|
+
params=params,
|
|
193
|
+
json=json,
|
|
194
|
+
headers=self._headers(auth_required),
|
|
195
|
+
timeout=self.timeout,
|
|
196
|
+
)
|
|
197
|
+
except requests.Timeout as exc:
|
|
198
|
+
raise ServerError(
|
|
199
|
+
f"Request to {path} timed out after {self.timeout}s.",
|
|
200
|
+
code="client_timeout",
|
|
201
|
+
hint="Increase TradeOddsClient(timeout=...) or narrow your filters.",
|
|
202
|
+
status_code=None,
|
|
203
|
+
) from exc
|
|
204
|
+
except requests.RequestException as exc:
|
|
205
|
+
raise ApiError(
|
|
206
|
+
f"Network error talking to TradeOdds: {exc}",
|
|
207
|
+
code="network_error",
|
|
208
|
+
hint="Check your connection and TRADEODDS_BASE_URL.",
|
|
209
|
+
) from exc
|
|
210
|
+
|
|
211
|
+
if resp.status_code >= 400:
|
|
212
|
+
self._raise_for_status(resp)
|
|
213
|
+
|
|
214
|
+
if not resp.content:
|
|
215
|
+
return None
|
|
216
|
+
try:
|
|
217
|
+
return resp.json()
|
|
218
|
+
except ValueError as exc: # malformed JSON — should never happen
|
|
219
|
+
raise ServerError(
|
|
220
|
+
f"Invalid JSON response from {path}: {exc}",
|
|
221
|
+
code="invalid_response",
|
|
222
|
+
status_code=resp.status_code,
|
|
223
|
+
) from exc
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _raise_for_status(resp: requests.Response) -> None:
|
|
227
|
+
"""Translate an error response into a typed SDK exception."""
|
|
228
|
+
request_id = resp.headers.get("X-Request-Id")
|
|
229
|
+
try:
|
|
230
|
+
payload = resp.json()
|
|
231
|
+
except ValueError:
|
|
232
|
+
payload = {}
|
|
233
|
+
|
|
234
|
+
envelope = payload.get("error") if isinstance(payload, dict) else None
|
|
235
|
+
if isinstance(envelope, dict):
|
|
236
|
+
code = envelope.get("code") or "api_error"
|
|
237
|
+
message = envelope.get("message") or resp.reason or "API error."
|
|
238
|
+
hint = envelope.get("hint")
|
|
239
|
+
request_id = envelope.get("request_id") or request_id
|
|
240
|
+
else:
|
|
241
|
+
# Non-v1 routes still use FastAPI's `{"detail": "..."}` shape.
|
|
242
|
+
code = "api_error"
|
|
243
|
+
detail = payload.get("detail") if isinstance(payload, dict) else None
|
|
244
|
+
message = detail if isinstance(detail, str) else (resp.reason or "API error.")
|
|
245
|
+
hint = None
|
|
246
|
+
|
|
247
|
+
exc_cls = _exception_for_status(resp.status_code)
|
|
248
|
+
raise exc_cls(
|
|
249
|
+
message,
|
|
250
|
+
code=code,
|
|
251
|
+
hint=hint,
|
|
252
|
+
request_id=request_id,
|
|
253
|
+
status_code=resp.status_code,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# ── Context-manager support ────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
def close(self) -> None:
|
|
259
|
+
self._session.close()
|
|
260
|
+
|
|
261
|
+
def __enter__(self) -> "TradeOddsClient":
|
|
262
|
+
return self
|
|
263
|
+
|
|
264
|
+
def __exit__(self, *_exc: Any) -> None:
|
|
265
|
+
self.close()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
__all__ = ["TradeOddsClient", "DEFAULT_BASE_URL", "__version__"]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Typed exceptions for the TradeOdds SDK.
|
|
2
|
+
|
|
3
|
+
All errors raised by `TradeOddsClient` inherit from `ApiError`. The structured
|
|
4
|
+
error envelope returned by the API (`code`, `message`, `hint`, `request_id`)
|
|
5
|
+
is preserved as attributes so callers and LLM agents can branch on the error
|
|
6
|
+
code rather than parsing prose.
|
|
7
|
+
"""
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ApiError(Exception):
|
|
12
|
+
"""Base class for all TradeOdds API errors.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
code: Machine-readable error code (e.g. ``rate_limit_exceeded``).
|
|
16
|
+
message: Human-readable error message from the API.
|
|
17
|
+
hint: Suggested remediation, if the API provided one.
|
|
18
|
+
request_id: Server-side request id for support contact.
|
|
19
|
+
status_code: HTTP status code that triggered the error.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
message: str,
|
|
25
|
+
*,
|
|
26
|
+
code: str = "api_error",
|
|
27
|
+
hint: Optional[str] = None,
|
|
28
|
+
request_id: Optional[str] = None,
|
|
29
|
+
status_code: Optional[int] = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
self.code = code
|
|
33
|
+
self.message = message
|
|
34
|
+
self.hint = hint
|
|
35
|
+
self.request_id = request_id
|
|
36
|
+
self.status_code = status_code
|
|
37
|
+
|
|
38
|
+
def __repr__(self) -> str:
|
|
39
|
+
return (
|
|
40
|
+
f"{self.__class__.__name__}(code={self.code!r}, "
|
|
41
|
+
f"status_code={self.status_code!r}, message={self.message!r})"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AuthError(ApiError):
|
|
46
|
+
"""Raised on 401 — missing or invalid API key."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class NotFoundError(ApiError):
|
|
50
|
+
"""Raised on 404 — unknown symbol or resource."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RateLimitError(ApiError):
|
|
54
|
+
"""Raised on 429 — daily or per-minute quota exceeded."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ValidationError(ApiError):
|
|
58
|
+
"""Raised on 422 / 400 — invalid request body or parameters."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ServerError(ApiError):
|
|
62
|
+
"""Raised on 5xx — server-side failure or timeout (504)."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
"ApiError",
|
|
67
|
+
"AuthError",
|
|
68
|
+
"NotFoundError",
|
|
69
|
+
"RateLimitError",
|
|
70
|
+
"ValidationError",
|
|
71
|
+
"ServerError",
|
|
72
|
+
]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""TypedDict definitions for TradeOdds request/response shapes.
|
|
2
|
+
|
|
3
|
+
These mirror the Pydantic models in ``backend/app/api/public_api.py``. Using
|
|
4
|
+
TypedDicts (instead of dataclasses) keeps the SDK zero-runtime-cost: requests
|
|
5
|
+
and responses round-trip as plain dicts, but editor / mypy autocomplete still
|
|
6
|
+
works.
|
|
7
|
+
|
|
8
|
+
For analyze / factor-match responses the server returns a rich nested object
|
|
9
|
+
that varies by tier — those are typed as ``Dict[str, Any]`` here so the SDK
|
|
10
|
+
doesn't drift when the response schema is extended. Inspect ``result.keys()``
|
|
11
|
+
or ``json.dumps(result, indent=2)`` on a real call to discover fields.
|
|
12
|
+
"""
|
|
13
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
# Python 3.11+: TypedDict supports total=False per-key with NotRequired.
|
|
17
|
+
from typing import NotRequired, TypedDict
|
|
18
|
+
except ImportError: # pragma: no cover - Python 3.9 / 3.10
|
|
19
|
+
from typing_extensions import NotRequired, TypedDict # type: ignore[assignment]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ── Reference / forward windows ────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
ReferencePeriod = Literal["1d", "2d", "3d", "4d", "5d", "1w", "1m"]
|
|
25
|
+
ForwardPeriod = Literal["1d", "2d", "3d", "4d", "5d", "20d", "1w", "1m"]
|
|
26
|
+
LookbackYears = Literal["1y", "3y", "5y", "10y", "20y", "max"]
|
|
27
|
+
PerfMetric = Literal["win_1d", "win_5d", "win_20d", "mean_1d", "mean_5d", "mean_20d"]
|
|
28
|
+
PerfOperator = Literal["gt", "lt"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ── Symbols ────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
class Symbol(TypedDict):
|
|
34
|
+
symbol: str
|
|
35
|
+
name: Optional[str]
|
|
36
|
+
sector: Optional[str]
|
|
37
|
+
is_high_liquidity: bool
|
|
38
|
+
is_etf: bool
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SymbolListResponse(TypedDict):
|
|
42
|
+
count: int
|
|
43
|
+
symbols: List[Symbol]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ── Analyze / factor-match request shapes ──────────────────────────────
|
|
47
|
+
|
|
48
|
+
class DNAConditions(TypedDict, total=False):
|
|
49
|
+
"""DNA filter flags accepted by /analyze and /factor-match.
|
|
50
|
+
|
|
51
|
+
Every key is optional — omit or set ``False`` to ignore a factor. ``daily_change``
|
|
52
|
+
defaults to True server-side. ``magnitude_mode`` only applies when ``magnitude``
|
|
53
|
+
is True.
|
|
54
|
+
"""
|
|
55
|
+
daily_change: bool
|
|
56
|
+
magnitude: bool
|
|
57
|
+
magnitude_mode: Literal["add", "replace"]
|
|
58
|
+
vix_level: bool
|
|
59
|
+
vix_move: bool
|
|
60
|
+
regime: bool
|
|
61
|
+
rel_vol: bool
|
|
62
|
+
rsi_zone: bool
|
|
63
|
+
rsi_slope: bool
|
|
64
|
+
structure_precision: bool
|
|
65
|
+
earnings_prox: bool
|
|
66
|
+
streak: bool
|
|
67
|
+
volume_streak: bool
|
|
68
|
+
earnings_perf: bool
|
|
69
|
+
overnight_gap: bool
|
|
70
|
+
analyst_trend: bool
|
|
71
|
+
month_of_year: bool
|
|
72
|
+
macro_risk: bool
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ScreenerFilters(TypedDict, total=False):
|
|
76
|
+
is_etf: bool
|
|
77
|
+
is_crypto: bool
|
|
78
|
+
is_gold_standard: bool
|
|
79
|
+
sector: str
|
|
80
|
+
industry: str
|
|
81
|
+
symbols: List[str]
|
|
82
|
+
price_min: float
|
|
83
|
+
price_max: float
|
|
84
|
+
volume_min: float
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class PerfFilter(TypedDict):
|
|
88
|
+
metric: PerfMetric
|
|
89
|
+
operator: PerfOperator
|
|
90
|
+
threshold: float
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Response payloads vary by tier and feature flag — keep them open so the SDK
|
|
94
|
+
# doesn't break when the server adds fields.
|
|
95
|
+
AnalyzeResult = Dict[str, Any]
|
|
96
|
+
FactorMatchResult = Dict[str, Any]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
__all__ = [
|
|
100
|
+
"ReferencePeriod",
|
|
101
|
+
"ForwardPeriod",
|
|
102
|
+
"LookbackYears",
|
|
103
|
+
"PerfMetric",
|
|
104
|
+
"PerfOperator",
|
|
105
|
+
"Symbol",
|
|
106
|
+
"SymbolListResponse",
|
|
107
|
+
"DNAConditions",
|
|
108
|
+
"ScreenerFilters",
|
|
109
|
+
"PerfFilter",
|
|
110
|
+
"AnalyzeResult",
|
|
111
|
+
"FactorMatchResult",
|
|
112
|
+
]
|