fivetwenty 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.
- fivetwenty-0.1.0/LICENSE +21 -0
- fivetwenty-0.1.0/PKG-INFO +309 -0
- fivetwenty-0.1.0/README.md +281 -0
- fivetwenty-0.1.0/fivetwenty/__init__.py +62 -0
- fivetwenty-0.1.0/fivetwenty/_internal/__init__.py +1 -0
- fivetwenty-0.1.0/fivetwenty/_internal/environment.py +17 -0
- fivetwenty-0.1.0/fivetwenty/_internal/utils.py +113 -0
- fivetwenty-0.1.0/fivetwenty/client.py +567 -0
- fivetwenty-0.1.0/fivetwenty/configuration.py +215 -0
- fivetwenty-0.1.0/fivetwenty/endpoints/__init__.py +1 -0
- fivetwenty-0.1.0/fivetwenty/endpoints/accounts.py +183 -0
- fivetwenty-0.1.0/fivetwenty/endpoints/instruments.py +106 -0
- fivetwenty-0.1.0/fivetwenty/endpoints/orders.py +656 -0
- fivetwenty-0.1.0/fivetwenty/endpoints/positions.py +164 -0
- fivetwenty-0.1.0/fivetwenty/endpoints/pricing.py +360 -0
- fivetwenty-0.1.0/fivetwenty/endpoints/trades.py +248 -0
- fivetwenty-0.1.0/fivetwenty/endpoints/transactions.py +271 -0
- fivetwenty-0.1.0/fivetwenty/exceptions.py +198 -0
- fivetwenty-0.1.0/fivetwenty/models/__init__.py +210 -0
- fivetwenty-0.1.0/fivetwenty/models/accounts.py +263 -0
- fivetwenty-0.1.0/fivetwenty/models/base.py +58 -0
- fivetwenty-0.1.0/fivetwenty/models/enums.py +494 -0
- fivetwenty-0.1.0/fivetwenty/models/error_codes.py +225 -0
- fivetwenty-0.1.0/fivetwenty/models/error_details.py +290 -0
- fivetwenty-0.1.0/fivetwenty/models/instruments.py +100 -0
- fivetwenty-0.1.0/fivetwenty/models/orders.py +620 -0
- fivetwenty-0.1.0/fivetwenty/models/positions.py +69 -0
- fivetwenty-0.1.0/fivetwenty/models/pricing.py +140 -0
- fivetwenty-0.1.0/fivetwenty/models/streaming.py +42 -0
- fivetwenty-0.1.0/fivetwenty/models/trades.py +99 -0
- fivetwenty-0.1.0/fivetwenty/models/transactions.py +540 -0
- fivetwenty-0.1.0/fivetwenty/py.typed +1 -0
- fivetwenty-0.1.0/fivetwenty.egg-info/PKG-INFO +309 -0
- fivetwenty-0.1.0/fivetwenty.egg-info/SOURCES.txt +37 -0
- fivetwenty-0.1.0/fivetwenty.egg-info/dependency_links.txt +1 -0
- fivetwenty-0.1.0/fivetwenty.egg-info/requires.txt +2 -0
- fivetwenty-0.1.0/fivetwenty.egg-info/top_level.txt +1 -0
- fivetwenty-0.1.0/pyproject.toml +291 -0
- fivetwenty-0.1.0/setup.cfg +4 -0
fivetwenty-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 FiveTwenty Team
|
|
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.
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fivetwenty
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Simple, elegant Python client for OANDA's REST API v20
|
|
5
|
+
Author: FiveTwenty Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/NimbleOx/fivetwenty
|
|
8
|
+
Project-URL: Repository, https://github.com/NimbleOx/fivetwenty
|
|
9
|
+
Project-URL: Documentation, https://github.com/NimbleOx/fivetwenty
|
|
10
|
+
Project-URL: Bug Reports, https://github.com/NimbleOx/fivetwenty/issues
|
|
11
|
+
Keywords: oanda,trading,forex,api,finance
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: httpx>=0.25.0
|
|
26
|
+
Requires-Dist: pydantic>=2.5.0
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# FiveTwenty
|
|
30
|
+
|
|
31
|
+
A comprehensive, production-ready Python client for the OANDA REST v20.
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Async-first** with sync wrapper
|
|
36
|
+
- **Type-safe** with full mypy strict compliance and 75+ comprehensive models
|
|
37
|
+
- **Minimal dependencies** (only httpx + pydantic)
|
|
38
|
+
- **Production ready** with retries, rate limiting, and comprehensive error handling
|
|
39
|
+
- **Reliable streaming** with configurable reconnection policies and heartbeat monitoring
|
|
40
|
+
- **Financial precision** with Decimal calculations and proper OANDA field aliases
|
|
41
|
+
- **Complete API coverage** with 100% endpoint implementation (all 7 endpoint groups)
|
|
42
|
+
- **Extensive testing** with 427 comprehensive tests and roundtrip validation
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
### Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Note: Package not yet published to PyPI - install from source
|
|
50
|
+
git clone https://github.com/NimbleOx/fivetwenty.git
|
|
51
|
+
cd fivetwenty
|
|
52
|
+
uv pip install -e .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Async Usage (Recommended)
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import asyncio
|
|
59
|
+
from decimal import Decimal
|
|
60
|
+
from fivetwenty import AsyncClient, Environment
|
|
61
|
+
|
|
62
|
+
async def main():
|
|
63
|
+
async with AsyncClient(
|
|
64
|
+
token="your-token-here",
|
|
65
|
+
environment=Environment.PRACTICE
|
|
66
|
+
) as client:
|
|
67
|
+
|
|
68
|
+
# Get accounts
|
|
69
|
+
accounts = await client.accounts.list()
|
|
70
|
+
account_id = accounts[0].id
|
|
71
|
+
|
|
72
|
+
# Create a market order
|
|
73
|
+
order = await client.orders.post_market_order(
|
|
74
|
+
account_id=account_id,
|
|
75
|
+
instrument="EUR_USD",
|
|
76
|
+
units=1000,
|
|
77
|
+
stop_loss=Decimal("1.0900"),
|
|
78
|
+
take_profit=Decimal("1.1100"),
|
|
79
|
+
)
|
|
80
|
+
print(f"Order created: {order.last_transaction_id}")
|
|
81
|
+
|
|
82
|
+
# Stream prices for 30 seconds
|
|
83
|
+
import time
|
|
84
|
+
end_time = time.time() + 30
|
|
85
|
+
|
|
86
|
+
async for price in client.pricing.stream(account_id, ["EUR_USD"]):
|
|
87
|
+
if hasattr(price, 'instrument'): # It's a price update
|
|
88
|
+
spread = price.spread
|
|
89
|
+
print(f"{price.instrument}: {price.closeout_bid}/{price.closeout_ask} (spread: {spread})")
|
|
90
|
+
|
|
91
|
+
if time.time() > end_time:
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
asyncio.run(main())
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Sync Usage
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from decimal import Decimal
|
|
102
|
+
from fivetwenty import Client, Environment
|
|
103
|
+
|
|
104
|
+
with Client(token="your-token-here", environment=Environment.PRACTICE) as client:
|
|
105
|
+
# Get accounts
|
|
106
|
+
accounts = client.accounts.list()
|
|
107
|
+
account_id = accounts[0].id
|
|
108
|
+
|
|
109
|
+
# Create a market order
|
|
110
|
+
order = client.orders.post_market_order(
|
|
111
|
+
account_id=account_id,
|
|
112
|
+
instrument="EUR_USD",
|
|
113
|
+
units=1000,
|
|
114
|
+
stop_loss=Decimal("1.0900")
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Stream prices (blocking iterator)
|
|
118
|
+
count = 0
|
|
119
|
+
for price in client.pricing.stream_iter(account_id, ["EUR_USD"]):
|
|
120
|
+
if hasattr(price, 'instrument'):
|
|
121
|
+
print(f"{price.instrument}: {price.closeout_bid}/{price.closeout_ask}")
|
|
122
|
+
|
|
123
|
+
count += 1
|
|
124
|
+
if count > 10:
|
|
125
|
+
break # Stop after 10 updates
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Configuration
|
|
129
|
+
|
|
130
|
+
### Environment Variables
|
|
131
|
+
|
|
132
|
+
- `FIVETWENTY_OANDA_TOKEN`: Your API token
|
|
133
|
+
- `FIVETWENTY_USER_AGENT_EXTRA`: Additional user agent info
|
|
134
|
+
|
|
135
|
+
### Advanced Configuration
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from fivetwenty import AsyncClient, Environment
|
|
139
|
+
import httpx
|
|
140
|
+
|
|
141
|
+
client = AsyncClient(
|
|
142
|
+
token="your-token",
|
|
143
|
+
environment=Environment.LIVE, # Use live trading
|
|
144
|
+
timeout=60.0, # 60 second timeout
|
|
145
|
+
max_retries=5, # Retry failed requests
|
|
146
|
+
|
|
147
|
+
# Custom HTTP client with proxy
|
|
148
|
+
transport=httpx.AsyncClient(
|
|
149
|
+
proxies="http://proxy.example.com:8080",
|
|
150
|
+
verify="/path/to/ca-bundle.crt"
|
|
151
|
+
),
|
|
152
|
+
|
|
153
|
+
# Custom logging
|
|
154
|
+
logger=your_logger,
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Error Handling
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from fivetwenty import VeeTwentyError, StreamStall
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
order = await client.orders.post_market_order(...)
|
|
165
|
+
except VeeTwentyError as e:
|
|
166
|
+
print(f"API Error: {e}")
|
|
167
|
+
print(f"Status: {e.status}")
|
|
168
|
+
print(f"Code: {e.code}")
|
|
169
|
+
print(f"Request ID: {e.request_id}")
|
|
170
|
+
|
|
171
|
+
if e.retryable:
|
|
172
|
+
# Can retry this operation
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
async for price in client.pricing.stream(...):
|
|
177
|
+
process(price)
|
|
178
|
+
except StreamStall:
|
|
179
|
+
# Reconnect and try again
|
|
180
|
+
pass
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Architecture Highlights
|
|
184
|
+
|
|
185
|
+
### Production-Ready Features
|
|
186
|
+
- **Smart retries** with exponential backoff and jitter
|
|
187
|
+
- **Rate limiting respect** honoring server `Retry-After` headers
|
|
188
|
+
- **Write-safe retries** - only retry POST/PUT/PATCH/DELETE with idempotency keys
|
|
189
|
+
- **Stall detection** using monotonic time for reliable stream monitoring
|
|
190
|
+
- **Token hygiene** - never logs sensitive authentication data
|
|
191
|
+
|
|
192
|
+
### Financial Precision
|
|
193
|
+
- **Decimal precision** for all monetary calculations (never float)
|
|
194
|
+
- **OANDA API compatibility** with proper camelCase field aliases
|
|
195
|
+
- **String serialization** of Decimals to prevent floating-point errors
|
|
196
|
+
- **Roundtrip validation** ensuring data integrity with OANDA's API format
|
|
197
|
+
|
|
198
|
+
### Developer Experience
|
|
199
|
+
- **Type safety** with full mypy strict compliance and `py.typed` marker
|
|
200
|
+
- **Comprehensive models** - 75+ Pydantic models covering the entire OANDA API
|
|
201
|
+
- **Intuitive API** - everything hangs off `client.accounts`, `client.orders`, `client.pricing`
|
|
202
|
+
- **Context managers** for automatic resource cleanup
|
|
203
|
+
- **Rich error messages** with request IDs and actionable information
|
|
204
|
+
- **VS Code ready** with included development environment configuration
|
|
205
|
+
|
|
206
|
+
## Project Structure
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
fivetwenty/
|
|
210
|
+
โโโ __init__.py # Clean public API
|
|
211
|
+
โโโ client.py # AsyncClient & Client implementations
|
|
212
|
+
โโโ exceptions.py # Error handling with VeeTwentyError
|
|
213
|
+
โโโ models.py # 75+ comprehensive OANDA API models
|
|
214
|
+
โโโ endpoints/ # Complete endpoint implementations
|
|
215
|
+
โ โโโ accounts.py # Account operations & configuration
|
|
216
|
+
โ โโโ orders.py # Complete order management
|
|
217
|
+
โ โโโ pricing.py # Pricing, streaming & candles
|
|
218
|
+
โ โโโ trades.py # Trade management
|
|
219
|
+
โ โโโ positions.py # Position operations
|
|
220
|
+
โ โโโ transactions.py # Transaction history & streaming
|
|
221
|
+
โโโ _internal/ # Internal utilities
|
|
222
|
+
โโโ environment.py # Environment enum
|
|
223
|
+
โโโ utils.py # Helper functions
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Requirements
|
|
227
|
+
|
|
228
|
+
- Python 3.10+
|
|
229
|
+
- httpx >= 0.25.0
|
|
230
|
+
- pydantic >= 2.5.0
|
|
231
|
+
|
|
232
|
+
## API Coverage
|
|
233
|
+
|
|
234
|
+
### โ
Complete OANDA v20 REST API Implementation (100%)
|
|
235
|
+
|
|
236
|
+
- **Account Management**: Complete account operations, configuration updates, and change polling
|
|
237
|
+
- **Order Operations**: Full order lifecycle - create, list, get, cancel, replace, and client extensions
|
|
238
|
+
- **Trade Management**: Complete trade operations - list, get, close, modify, and dependent orders
|
|
239
|
+
- **Position Management**: Full position operations - list, get, close by instrument
|
|
240
|
+
- **Pricing & Streaming**: Real-time pricing, reliable streaming, and historical candles
|
|
241
|
+
- **Transaction History**: Complete audit trail, streaming, and incremental updates
|
|
242
|
+
|
|
243
|
+
**All 7 endpoint groups implemented with 268 comprehensive tests!**
|
|
244
|
+
|
|
245
|
+
## Development
|
|
246
|
+
|
|
247
|
+
This project uses **uv** for dependency management, **poethepoet** for task running, and **ruff** for formatting/linting:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# Quick setup (poethepoet)
|
|
251
|
+
poe setup # Complete project setup for new developers
|
|
252
|
+
poe dev # Fast development checks (format, typecheck, test)
|
|
253
|
+
poe check # Run format, lint, typecheck, and tests
|
|
254
|
+
|
|
255
|
+
# Testing
|
|
256
|
+
poe test # Run all tests
|
|
257
|
+
poe test-cov # Run tests with coverage
|
|
258
|
+
|
|
259
|
+
# Code quality
|
|
260
|
+
poe quality-core # Run format, lint, and typecheck (core files only)
|
|
261
|
+
poe format # Format code
|
|
262
|
+
poe lint-fix # Fix linting issues
|
|
263
|
+
|
|
264
|
+
# Documentation
|
|
265
|
+
poe docs-serve # Serve docs locally
|
|
266
|
+
poe docs-build # Build documentation
|
|
267
|
+
|
|
268
|
+
# Or use uv directly
|
|
269
|
+
uv sync # Install dependencies
|
|
270
|
+
uv run pytest # Run tests
|
|
271
|
+
uv run ruff format . # Format code
|
|
272
|
+
uv run mypy fivetwenty/ # Type checking
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
See [CLAUDE.md](CLAUDE.md) for detailed development guidance and [TODO.md](TODO.md) for planned features.
|
|
276
|
+
|
|
277
|
+
## ๐ Documentation
|
|
278
|
+
|
|
279
|
+
This project features comprehensive documentation organized using the **Diรกtaxis framework** - a systematic approach that organizes content by user needs.
|
|
280
|
+
|
|
281
|
+
### Documentation Structure
|
|
282
|
+
|
|
283
|
+
| | **PRACTICAL USE** | **THEORETICAL KNOWLEDGE** |
|
|
284
|
+
|-------------------|-----------------------------------|---------------------------------|
|
|
285
|
+
| **LEARNING-ORIENTED** | ๐ **TUTORIALS**<br>(Learning by doing) | ๐ **EXPLANATION**<br>(Understanding) |
|
|
286
|
+
| **PROBLEM-ORIENTED** | ๐ ๏ธ **HOW-TO GUIDES**<br>(Solving problems) | ๐ **REFERENCE**<br>(Information lookup) |
|
|
287
|
+
|
|
288
|
+
### Working with Documentation
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# Install documentation dependencies
|
|
292
|
+
uv pip install -e .[docs]
|
|
293
|
+
|
|
294
|
+
# Serve documentation locally (available at http://localhost:8000)
|
|
295
|
+
uv run mkdocs serve
|
|
296
|
+
|
|
297
|
+
# Build documentation for production
|
|
298
|
+
uv run mkdocs build
|
|
299
|
+
|
|
300
|
+
# Or use poe tasks
|
|
301
|
+
uv run poe docs-serve # Serve locally
|
|
302
|
+
uv run poe docs-build # Build for production
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The documentation includes tutorials, how-to guides, API reference, and conceptual explanations. Visit the documentation site for complete details.
|
|
306
|
+
|
|
307
|
+
## License
|
|
308
|
+
|
|
309
|
+
MIT License - see LICENSE file for details.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# FiveTwenty
|
|
2
|
+
|
|
3
|
+
A comprehensive, production-ready Python client for the OANDA REST v20.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Async-first** with sync wrapper
|
|
8
|
+
- **Type-safe** with full mypy strict compliance and 75+ comprehensive models
|
|
9
|
+
- **Minimal dependencies** (only httpx + pydantic)
|
|
10
|
+
- **Production ready** with retries, rate limiting, and comprehensive error handling
|
|
11
|
+
- **Reliable streaming** with configurable reconnection policies and heartbeat monitoring
|
|
12
|
+
- **Financial precision** with Decimal calculations and proper OANDA field aliases
|
|
13
|
+
- **Complete API coverage** with 100% endpoint implementation (all 7 endpoint groups)
|
|
14
|
+
- **Extensive testing** with 427 comprehensive tests and roundtrip validation
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Note: Package not yet published to PyPI - install from source
|
|
22
|
+
git clone https://github.com/NimbleOx/fivetwenty.git
|
|
23
|
+
cd fivetwenty
|
|
24
|
+
uv pip install -e .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Async Usage (Recommended)
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import asyncio
|
|
31
|
+
from decimal import Decimal
|
|
32
|
+
from fivetwenty import AsyncClient, Environment
|
|
33
|
+
|
|
34
|
+
async def main():
|
|
35
|
+
async with AsyncClient(
|
|
36
|
+
token="your-token-here",
|
|
37
|
+
environment=Environment.PRACTICE
|
|
38
|
+
) as client:
|
|
39
|
+
|
|
40
|
+
# Get accounts
|
|
41
|
+
accounts = await client.accounts.list()
|
|
42
|
+
account_id = accounts[0].id
|
|
43
|
+
|
|
44
|
+
# Create a market order
|
|
45
|
+
order = await client.orders.post_market_order(
|
|
46
|
+
account_id=account_id,
|
|
47
|
+
instrument="EUR_USD",
|
|
48
|
+
units=1000,
|
|
49
|
+
stop_loss=Decimal("1.0900"),
|
|
50
|
+
take_profit=Decimal("1.1100"),
|
|
51
|
+
)
|
|
52
|
+
print(f"Order created: {order.last_transaction_id}")
|
|
53
|
+
|
|
54
|
+
# Stream prices for 30 seconds
|
|
55
|
+
import time
|
|
56
|
+
end_time = time.time() + 30
|
|
57
|
+
|
|
58
|
+
async for price in client.pricing.stream(account_id, ["EUR_USD"]):
|
|
59
|
+
if hasattr(price, 'instrument'): # It's a price update
|
|
60
|
+
spread = price.spread
|
|
61
|
+
print(f"{price.instrument}: {price.closeout_bid}/{price.closeout_ask} (spread: {spread})")
|
|
62
|
+
|
|
63
|
+
if time.time() > end_time:
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
asyncio.run(main())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Sync Usage
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from decimal import Decimal
|
|
74
|
+
from fivetwenty import Client, Environment
|
|
75
|
+
|
|
76
|
+
with Client(token="your-token-here", environment=Environment.PRACTICE) as client:
|
|
77
|
+
# Get accounts
|
|
78
|
+
accounts = client.accounts.list()
|
|
79
|
+
account_id = accounts[0].id
|
|
80
|
+
|
|
81
|
+
# Create a market order
|
|
82
|
+
order = client.orders.post_market_order(
|
|
83
|
+
account_id=account_id,
|
|
84
|
+
instrument="EUR_USD",
|
|
85
|
+
units=1000,
|
|
86
|
+
stop_loss=Decimal("1.0900")
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Stream prices (blocking iterator)
|
|
90
|
+
count = 0
|
|
91
|
+
for price in client.pricing.stream_iter(account_id, ["EUR_USD"]):
|
|
92
|
+
if hasattr(price, 'instrument'):
|
|
93
|
+
print(f"{price.instrument}: {price.closeout_bid}/{price.closeout_ask}")
|
|
94
|
+
|
|
95
|
+
count += 1
|
|
96
|
+
if count > 10:
|
|
97
|
+
break # Stop after 10 updates
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
### Environment Variables
|
|
103
|
+
|
|
104
|
+
- `FIVETWENTY_OANDA_TOKEN`: Your API token
|
|
105
|
+
- `FIVETWENTY_USER_AGENT_EXTRA`: Additional user agent info
|
|
106
|
+
|
|
107
|
+
### Advanced Configuration
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from fivetwenty import AsyncClient, Environment
|
|
111
|
+
import httpx
|
|
112
|
+
|
|
113
|
+
client = AsyncClient(
|
|
114
|
+
token="your-token",
|
|
115
|
+
environment=Environment.LIVE, # Use live trading
|
|
116
|
+
timeout=60.0, # 60 second timeout
|
|
117
|
+
max_retries=5, # Retry failed requests
|
|
118
|
+
|
|
119
|
+
# Custom HTTP client with proxy
|
|
120
|
+
transport=httpx.AsyncClient(
|
|
121
|
+
proxies="http://proxy.example.com:8080",
|
|
122
|
+
verify="/path/to/ca-bundle.crt"
|
|
123
|
+
),
|
|
124
|
+
|
|
125
|
+
# Custom logging
|
|
126
|
+
logger=your_logger,
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Error Handling
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from fivetwenty import VeeTwentyError, StreamStall
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
order = await client.orders.post_market_order(...)
|
|
137
|
+
except VeeTwentyError as e:
|
|
138
|
+
print(f"API Error: {e}")
|
|
139
|
+
print(f"Status: {e.status}")
|
|
140
|
+
print(f"Code: {e.code}")
|
|
141
|
+
print(f"Request ID: {e.request_id}")
|
|
142
|
+
|
|
143
|
+
if e.retryable:
|
|
144
|
+
# Can retry this operation
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
async for price in client.pricing.stream(...):
|
|
149
|
+
process(price)
|
|
150
|
+
except StreamStall:
|
|
151
|
+
# Reconnect and try again
|
|
152
|
+
pass
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Architecture Highlights
|
|
156
|
+
|
|
157
|
+
### Production-Ready Features
|
|
158
|
+
- **Smart retries** with exponential backoff and jitter
|
|
159
|
+
- **Rate limiting respect** honoring server `Retry-After` headers
|
|
160
|
+
- **Write-safe retries** - only retry POST/PUT/PATCH/DELETE with idempotency keys
|
|
161
|
+
- **Stall detection** using monotonic time for reliable stream monitoring
|
|
162
|
+
- **Token hygiene** - never logs sensitive authentication data
|
|
163
|
+
|
|
164
|
+
### Financial Precision
|
|
165
|
+
- **Decimal precision** for all monetary calculations (never float)
|
|
166
|
+
- **OANDA API compatibility** with proper camelCase field aliases
|
|
167
|
+
- **String serialization** of Decimals to prevent floating-point errors
|
|
168
|
+
- **Roundtrip validation** ensuring data integrity with OANDA's API format
|
|
169
|
+
|
|
170
|
+
### Developer Experience
|
|
171
|
+
- **Type safety** with full mypy strict compliance and `py.typed` marker
|
|
172
|
+
- **Comprehensive models** - 75+ Pydantic models covering the entire OANDA API
|
|
173
|
+
- **Intuitive API** - everything hangs off `client.accounts`, `client.orders`, `client.pricing`
|
|
174
|
+
- **Context managers** for automatic resource cleanup
|
|
175
|
+
- **Rich error messages** with request IDs and actionable information
|
|
176
|
+
- **VS Code ready** with included development environment configuration
|
|
177
|
+
|
|
178
|
+
## Project Structure
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
fivetwenty/
|
|
182
|
+
โโโ __init__.py # Clean public API
|
|
183
|
+
โโโ client.py # AsyncClient & Client implementations
|
|
184
|
+
โโโ exceptions.py # Error handling with VeeTwentyError
|
|
185
|
+
โโโ models.py # 75+ comprehensive OANDA API models
|
|
186
|
+
โโโ endpoints/ # Complete endpoint implementations
|
|
187
|
+
โ โโโ accounts.py # Account operations & configuration
|
|
188
|
+
โ โโโ orders.py # Complete order management
|
|
189
|
+
โ โโโ pricing.py # Pricing, streaming & candles
|
|
190
|
+
โ โโโ trades.py # Trade management
|
|
191
|
+
โ โโโ positions.py # Position operations
|
|
192
|
+
โ โโโ transactions.py # Transaction history & streaming
|
|
193
|
+
โโโ _internal/ # Internal utilities
|
|
194
|
+
โโโ environment.py # Environment enum
|
|
195
|
+
โโโ utils.py # Helper functions
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Requirements
|
|
199
|
+
|
|
200
|
+
- Python 3.10+
|
|
201
|
+
- httpx >= 0.25.0
|
|
202
|
+
- pydantic >= 2.5.0
|
|
203
|
+
|
|
204
|
+
## API Coverage
|
|
205
|
+
|
|
206
|
+
### โ
Complete OANDA v20 REST API Implementation (100%)
|
|
207
|
+
|
|
208
|
+
- **Account Management**: Complete account operations, configuration updates, and change polling
|
|
209
|
+
- **Order Operations**: Full order lifecycle - create, list, get, cancel, replace, and client extensions
|
|
210
|
+
- **Trade Management**: Complete trade operations - list, get, close, modify, and dependent orders
|
|
211
|
+
- **Position Management**: Full position operations - list, get, close by instrument
|
|
212
|
+
- **Pricing & Streaming**: Real-time pricing, reliable streaming, and historical candles
|
|
213
|
+
- **Transaction History**: Complete audit trail, streaming, and incremental updates
|
|
214
|
+
|
|
215
|
+
**All 7 endpoint groups implemented with 268 comprehensive tests!**
|
|
216
|
+
|
|
217
|
+
## Development
|
|
218
|
+
|
|
219
|
+
This project uses **uv** for dependency management, **poethepoet** for task running, and **ruff** for formatting/linting:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Quick setup (poethepoet)
|
|
223
|
+
poe setup # Complete project setup for new developers
|
|
224
|
+
poe dev # Fast development checks (format, typecheck, test)
|
|
225
|
+
poe check # Run format, lint, typecheck, and tests
|
|
226
|
+
|
|
227
|
+
# Testing
|
|
228
|
+
poe test # Run all tests
|
|
229
|
+
poe test-cov # Run tests with coverage
|
|
230
|
+
|
|
231
|
+
# Code quality
|
|
232
|
+
poe quality-core # Run format, lint, and typecheck (core files only)
|
|
233
|
+
poe format # Format code
|
|
234
|
+
poe lint-fix # Fix linting issues
|
|
235
|
+
|
|
236
|
+
# Documentation
|
|
237
|
+
poe docs-serve # Serve docs locally
|
|
238
|
+
poe docs-build # Build documentation
|
|
239
|
+
|
|
240
|
+
# Or use uv directly
|
|
241
|
+
uv sync # Install dependencies
|
|
242
|
+
uv run pytest # Run tests
|
|
243
|
+
uv run ruff format . # Format code
|
|
244
|
+
uv run mypy fivetwenty/ # Type checking
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
See [CLAUDE.md](CLAUDE.md) for detailed development guidance and [TODO.md](TODO.md) for planned features.
|
|
248
|
+
|
|
249
|
+
## ๐ Documentation
|
|
250
|
+
|
|
251
|
+
This project features comprehensive documentation organized using the **Diรกtaxis framework** - a systematic approach that organizes content by user needs.
|
|
252
|
+
|
|
253
|
+
### Documentation Structure
|
|
254
|
+
|
|
255
|
+
| | **PRACTICAL USE** | **THEORETICAL KNOWLEDGE** |
|
|
256
|
+
|-------------------|-----------------------------------|---------------------------------|
|
|
257
|
+
| **LEARNING-ORIENTED** | ๐ **TUTORIALS**<br>(Learning by doing) | ๐ **EXPLANATION**<br>(Understanding) |
|
|
258
|
+
| **PROBLEM-ORIENTED** | ๐ ๏ธ **HOW-TO GUIDES**<br>(Solving problems) | ๐ **REFERENCE**<br>(Information lookup) |
|
|
259
|
+
|
|
260
|
+
### Working with Documentation
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Install documentation dependencies
|
|
264
|
+
uv pip install -e .[docs]
|
|
265
|
+
|
|
266
|
+
# Serve documentation locally (available at http://localhost:8000)
|
|
267
|
+
uv run mkdocs serve
|
|
268
|
+
|
|
269
|
+
# Build documentation for production
|
|
270
|
+
uv run mkdocs build
|
|
271
|
+
|
|
272
|
+
# Or use poe tasks
|
|
273
|
+
uv run poe docs-serve # Serve locally
|
|
274
|
+
uv run poe docs-build # Build for production
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
The documentation includes tutorials, how-to guides, API reference, and conceptual explanations. Visit the documentation site for complete details.
|
|
278
|
+
|
|
279
|
+
## License
|
|
280
|
+
|
|
281
|
+
MIT License - see LICENSE file for details.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OANDA REST API v20 Python SDK
|
|
3
|
+
|
|
4
|
+
A simple, elegant Python client for OANDA's REST API v20.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from fivetwenty import Client, AsyncClient, Environment, AccountConfig
|
|
8
|
+
|
|
9
|
+
# Method 1: Direct parameters
|
|
10
|
+
async with AsyncClient(token="your-token", environment=Environment.PRACTICE) as client:
|
|
11
|
+
accounts = await client.accounts.list()
|
|
12
|
+
|
|
13
|
+
# Method 2: Configuration object
|
|
14
|
+
config = AccountConfig(
|
|
15
|
+
token="your-token",
|
|
16
|
+
account_id="your-account-id",
|
|
17
|
+
environment=Environment.PRACTICE,
|
|
18
|
+
alias="my_account"
|
|
19
|
+
)
|
|
20
|
+
async with AsyncClient(config=config) as client:
|
|
21
|
+
accounts = await client.accounts.list()
|
|
22
|
+
|
|
23
|
+
# Method 3: Environment variables (fallback)
|
|
24
|
+
# Set FIVETWENTY_OANDA_TOKEN, FIVETWENTY_OANDA_ACCOUNT, etc.
|
|
25
|
+
async with AsyncClient() as client:
|
|
26
|
+
accounts = await client.accounts.list()
|
|
27
|
+
|
|
28
|
+
# Sync wrapper (same patterns)
|
|
29
|
+
with Client(token="your-token") as client:
|
|
30
|
+
accounts = client.accounts.list()
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
__version__ = "20.1.0"
|
|
34
|
+
|
|
35
|
+
from ._internal.environment import Environment
|
|
36
|
+
from .client import AsyncClient, Client
|
|
37
|
+
from .configuration import AccountConfig, AccountConfigLoader, ConfigValidator
|
|
38
|
+
from .exceptions import FiveTwentyError, StreamStall
|
|
39
|
+
from .models import ErrorCategory, ErrorDetails, ErrorSeverity, FiveTwentyErrorCode, ValidationViolation
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
# Configuration
|
|
43
|
+
"AccountConfig",
|
|
44
|
+
"AccountConfigLoader",
|
|
45
|
+
# Main clients
|
|
46
|
+
"AsyncClient",
|
|
47
|
+
"Client",
|
|
48
|
+
"ConfigValidator",
|
|
49
|
+
# Enums
|
|
50
|
+
"Environment",
|
|
51
|
+
# Error handling
|
|
52
|
+
"ErrorCategory",
|
|
53
|
+
"ErrorDetails",
|
|
54
|
+
"ErrorSeverity",
|
|
55
|
+
# Exceptions
|
|
56
|
+
"FiveTwentyError",
|
|
57
|
+
"FiveTwentyErrorCode",
|
|
58
|
+
"StreamStall",
|
|
59
|
+
"ValidationViolation",
|
|
60
|
+
# Version
|
|
61
|
+
"__version__",
|
|
62
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Internal utilities and helpers."""
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Environment configuration."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Environment(Enum):
|
|
7
|
+
"""OANDA API environments."""
|
|
8
|
+
|
|
9
|
+
PRACTICE = "practice"
|
|
10
|
+
LIVE = "live"
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def base_url(self) -> str:
|
|
14
|
+
"""Get the base URL for this environment."""
|
|
15
|
+
if self == Environment.LIVE:
|
|
16
|
+
return "https://api-fxtrade.oanda.com/v3"
|
|
17
|
+
return "https://api-fxpractice.oanda.com/v3"
|