|
| 1 | +# Async/Await Refactoring Plan for project-x-py |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +This issue outlines a comprehensive plan to refactor the project-x-py SDK from synchronous to asynchronous operations, enabling better performance, resource utilization, and natural integration with real-time trading workflows. |
| 6 | + |
| 7 | +## Motivation |
| 8 | + |
| 9 | +The current synchronous architecture has several limitations: |
| 10 | + |
| 11 | +1. **Blocking I/O**: HTTP requests block the thread, preventing concurrent operations |
| 12 | +2. **Inefficient for Real-time**: SignalR/WebSocket connections naturally fit async patterns |
| 13 | +3. **Resource Utilization**: Can't efficiently handle multiple market data streams or order operations concurrently |
| 14 | +4. **Modern Python Standards**: Async is the standard for I/O-heavy Python applications, especially in financial/trading contexts |
| 15 | + |
| 16 | +## Benefits of Async Migration |
| 17 | + |
| 18 | +- **Concurrent Operations**: Execute multiple API calls simultaneously (e.g., fetch positions while placing orders) |
| 19 | +- **Non-blocking Real-time**: Process WebSocket events without blocking other operations |
| 20 | +- **Better Resource Usage**: Single thread can handle many concurrent connections |
| 21 | +- **Improved Responsiveness**: UI/strategy code won't freeze during API calls |
| 22 | +- **Natural Event Handling**: Async/await patterns match event-driven trading systems |
| 23 | + |
| 24 | +## Technical Analysis |
| 25 | + |
| 26 | +### Current Architecture |
| 27 | + |
| 28 | +1. **HTTP Client**: Uses `requests` library with session pooling |
| 29 | +2. **WebSocket**: Uses `signalrcore` for SignalR connections |
| 30 | +3. **Blocking Pattern**: All API calls block until completion |
| 31 | +4. **Managers**: OrderManager, PositionManager, etc. all use synchronous methods |
| 32 | + |
| 33 | +### Proposed Async Architecture |
| 34 | + |
| 35 | +1. **HTTP Client**: Migrate to `httpx` (supports both sync/async) |
| 36 | +2. **WebSocket**: Use `python-signalrcore-async` or create async wrapper for SignalR |
| 37 | +3. **Async Pattern**: All public APIs become `async def` methods |
| 38 | +4. **Managers**: Convert to async with proper concurrency handling |
| 39 | + |
| 40 | +## Implementation Plan |
| 41 | + |
| 42 | +### Phase 1: Foundation (Week 1-2) |
| 43 | + |
| 44 | +- [ ] Add async dependencies to `pyproject.toml`: |
| 45 | + - `httpx[http2]` for async HTTP with HTTP/2 support |
| 46 | + - `python-signalrcore-async` or evaluate alternatives |
| 47 | + - Update `pytest-asyncio` for testing |
| 48 | +- [ ] Create async base client class (`AsyncProjectX`) |
| 49 | +- [ ] Implement async session management and connection pooling |
| 50 | +- [ ] Design async error handling and retry logic |
| 51 | + |
| 52 | +### Phase 2: Core Client Migration (Week 2-3) |
| 53 | + |
| 54 | +- [ ] Convert authentication methods to async |
| 55 | +- [ ] Migrate account management endpoints |
| 56 | +- [ ] Convert market data methods (get_bars, get_instrument) |
| 57 | +- [ ] Implement async caching mechanisms |
| 58 | +- [ ] Add async rate limiting |
| 59 | + |
| 60 | +### Phase 3: Manager Migration (Week 3-4) |
| 61 | + |
| 62 | +- [ ] Convert OrderManager to async |
| 63 | +- [ ] Convert PositionManager to async |
| 64 | +- [ ] Convert RealtimeDataManager to async |
| 65 | +- [ ] Update OrderBook for async operations |
| 66 | +- [ ] Ensure managers can share async ProjectXRealtimeClient |
| 67 | + |
| 68 | +### Phase 4: SignalR/WebSocket Integration (Week 4-5) |
| 69 | + |
| 70 | +- [ ] Research SignalR async options: |
| 71 | + - Option A: `python-signalrcore-async` (if mature enough) |
| 72 | + - Option B: Create async wrapper around current `signalrcore` |
| 73 | + - Option C: Use `aiohttp` with custom SignalR protocol implementation |
| 74 | +- [ ] Implement async event handling |
| 75 | +- [ ] Convert callback system to async-friendly pattern (async generators?) |
| 76 | +- [ ] Test reconnection logic with async |
| 77 | + |
| 78 | +### Phase 5: Testing & Documentation (Week 5-6) |
| 79 | + |
| 80 | +- [ ] Convert all tests to async using `pytest-asyncio` |
| 81 | +- [ ] Add integration tests for concurrent operations |
| 82 | +- [ ] Update all examples to use async/await |
| 83 | +- [ ] Document migration guide for users |
| 84 | +- [ ] Performance benchmarks (sync vs async) |
| 85 | + |
| 86 | +## API Design Decisions |
| 87 | + |
| 88 | +### Option 1: Pure Async (Recommended per CLAUDE.md) |
| 89 | +```python |
| 90 | +# All methods become async |
| 91 | +client = AsyncProjectX(api_key, username) |
| 92 | +await client.authenticate() |
| 93 | +positions = await client.get_positions() |
| 94 | +``` |
| 95 | + |
| 96 | +### Option 2: Dual API (Not recommended due to complexity) |
| 97 | +```python |
| 98 | +# Both sync and async clients |
| 99 | +sync_client = ProjectX(api_key, username) |
| 100 | +async_client = AsyncProjectX(api_key, username) |
| 101 | +``` |
| 102 | + |
| 103 | +Given the CLAUDE.md directive for "No Backward Compatibility" and "Clean Code Priority", **Option 1 (Pure Async) is recommended**. |
| 104 | + |
| 105 | +## Breaking Changes |
| 106 | + |
| 107 | +This refactoring will introduce breaking changes: |
| 108 | + |
| 109 | +1. All public methods become `async` |
| 110 | +2. Clients must use `async with` for proper cleanup |
| 111 | +3. Event handlers must be async functions |
| 112 | +4. Example code and integrations need updates |
| 113 | + |
| 114 | +## Migration Guide (Draft) |
| 115 | + |
| 116 | +```python |
| 117 | +# Old (Sync) |
| 118 | +client = ProjectX(api_key, username) |
| 119 | +client.authenticate() |
| 120 | +positions = client.get_positions() |
| 121 | + |
| 122 | +# New (Async) |
| 123 | +async with AsyncProjectX(api_key, username) as client: |
| 124 | + await client.authenticate() |
| 125 | + positions = await client.get_positions() |
| 126 | +``` |
| 127 | + |
| 128 | +## Technical Considerations |
| 129 | + |
| 130 | +### SignalR Compatibility |
| 131 | + |
| 132 | +ProjectX requires SignalR for real-time connections. Options: |
| 133 | + |
| 134 | +1. **python-signalrcore-async**: Check maturity and compatibility |
| 135 | +2. **Async Wrapper**: Create async wrapper around sync signalrcore |
| 136 | +3. **Custom Implementation**: Use aiohttp with SignalR protocol (complex but most control) |
| 137 | + |
| 138 | +### Connection Management |
| 139 | + |
| 140 | +- Use async context managers for resource cleanup |
| 141 | +- Implement proper connection pooling for HTTP/2 |
| 142 | +- Handle WebSocket reconnection in async context |
| 143 | + |
| 144 | +### Performance Targets |
| 145 | + |
| 146 | +- Concurrent API calls should show 3-5x throughput improvement |
| 147 | +- WebSocket event processing latency < 1ms |
| 148 | +- Memory usage should remain comparable to sync version |
| 149 | + |
| 150 | +## Dependencies to Add |
| 151 | + |
| 152 | +```toml |
| 153 | +[project.dependencies] |
| 154 | +httpx = ">=0.27.0" |
| 155 | +# SignalR async solution (TBD based on research) |
| 156 | + |
| 157 | +[project.optional-dependencies.dev] |
| 158 | +pytest-asyncio = ">=0.23.0" |
| 159 | +aioresponses = ">=0.7.6" # For mocking async HTTP |
| 160 | +``` |
| 161 | + |
| 162 | +## Example: Async Trading Bot |
| 163 | + |
| 164 | +```python |
| 165 | +import asyncio |
| 166 | +from project_x_py import AsyncProjectX, create_async_trading_suite |
| 167 | + |
| 168 | +async def trading_bot(): |
| 169 | + async with AsyncProjectX(api_key, username) as client: |
| 170 | + await client.authenticate() |
| 171 | + |
| 172 | + # Create async trading suite |
| 173 | + suite = await create_async_trading_suite( |
| 174 | + instrument="MGC", |
| 175 | + project_x=client, |
| 176 | + jwt_token=client.session_token, |
| 177 | + account_id=client.account_info.id |
| 178 | + ) |
| 179 | + |
| 180 | + # Concurrent operations |
| 181 | + positions_task = asyncio.create_task( |
| 182 | + suite["position_manager"].get_positions() |
| 183 | + ) |
| 184 | + market_data_task = asyncio.create_task( |
| 185 | + suite["data_manager"].get_bars("1m", 100) |
| 186 | + ) |
| 187 | + |
| 188 | + positions, market_data = await asyncio.gather( |
| 189 | + positions_task, market_data_task |
| 190 | + ) |
| 191 | + |
| 192 | + # Real-time event handling |
| 193 | + async for tick in suite["data_manager"].stream_ticks(): |
| 194 | + await process_tick(tick) |
| 195 | + |
| 196 | +async def process_tick(tick): |
| 197 | + # Async tick processing |
| 198 | + pass |
| 199 | + |
| 200 | +if __name__ == "__main__": |
| 201 | + asyncio.run(trading_bot()) |
| 202 | +``` |
| 203 | + |
| 204 | +## Timeline |
| 205 | + |
| 206 | +- **Total Duration**: 5-6 weeks |
| 207 | +- **Testing Phase**: Additional 1 week |
| 208 | +- **Documentation**: Ongoing throughout |
| 209 | + |
| 210 | +## Success Criteria |
| 211 | + |
| 212 | +1. All tests pass with async implementation |
| 213 | +2. Performance benchmarks show improvement |
| 214 | +3. Real-time SignalR connections work reliably |
| 215 | +4. Clean async API without sync remnants |
| 216 | +5. Comprehensive documentation and examples |
| 217 | + |
| 218 | +## Open Questions |
| 219 | + |
| 220 | +1. Which SignalR async library to use? |
| 221 | +2. Should we provide any sync compatibility layer? |
| 222 | +3. How to handle existing users during transition? |
| 223 | +4. Performance benchmarking methodology? |
| 224 | + |
| 225 | +## References |
| 226 | + |
| 227 | +- [ProjectX SignalR Documentation](https://gateway.docs.projectx.com/docs/realtime/) |
| 228 | +- [httpx Documentation](https://www.python-httpx.org/) |
| 229 | +- [Python Async Best Practices](https://docs.python.org/3/library/asyncio-task.html) |
| 230 | +- [SignalR Protocol Specification](https://github.com/dotnet/aspnetcore/tree/main/src/SignalR/docs/specs) |
| 231 | + |
| 232 | +--- |
| 233 | + |
| 234 | +**Note**: This refactoring aligns with the CLAUDE.md directive for "No Backward Compatibility" and "Clean Code Priority" during active development. |
0 commit comments