-
-
Notifications
You must be signed in to change notification settings - Fork 11
Closed
Description
Async/Await Refactoring Plan for project-x-py
Summary
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.
Motivation
The current synchronous architecture has several limitations:
- Blocking I/O: HTTP requests block the thread, preventing concurrent operations
- Inefficient for Real-time: SignalR/WebSocket connections naturally fit async patterns
- Resource Utilization: Can't efficiently handle multiple market data streams or order operations concurrently
- Modern Python Standards: Async is the standard for I/O-heavy Python applications, especially in financial/trading contexts
Benefits of Async Migration
- Concurrent Operations: Execute multiple API calls simultaneously (e.g., fetch positions while placing orders)
- Non-blocking Real-time: Process WebSocket events without blocking other operations
- Better Resource Usage: Single thread can handle many concurrent connections
- Improved Responsiveness: UI/strategy code won't freeze during API calls
- Natural Event Handling: Async/await patterns match event-driven trading systems
Technical Analysis
Current Architecture
- HTTP Client: Uses
requestslibrary with session pooling - WebSocket: Uses
signalrcorefor SignalR connections - Blocking Pattern: All API calls block until completion
- Managers: OrderManager, PositionManager, etc. all use synchronous methods
Proposed Async Architecture
- HTTP Client: Migrate to
httpx(supports both sync/async) - WebSocket: Use
python-signalrcore-asyncor create async wrapper for SignalR - Async Pattern: All public APIs become
async defmethods - Managers: Convert to async with proper concurrency handling
Implementation Plan
Phase 1: Foundation (Week 1-2)
- Add async dependencies to
pyproject.toml:httpx[http2]for async HTTP with HTTP/2 supportpython-signalrcore-asyncor evaluate alternatives- Update
pytest-asynciofor testing
- Create async base client class (
AsyncProjectX) - Implement async session management and connection pooling
- Design async error handling and retry logic
Phase 2: Core Client Migration (Week 2-3)
- Convert authentication methods to async
- Migrate account management endpoints
- Convert market data methods (get_bars, get_instrument)
- Implement async caching mechanisms
- Add async rate limiting
Phase 3: Manager Migration (Week 3-4)
- Convert OrderManager to async
- Convert PositionManager to async
- Convert RealtimeDataManager to async
- Update OrderBook for async operations
- Ensure managers can share async ProjectXRealtimeClient
Phase 4: SignalR/WebSocket Integration (Week 4-5)
- Research SignalR async options:
- Option A:
python-signalrcore-async(if mature enough) - Option B: Create async wrapper around current
signalrcore - Option C: Use
aiohttpwith custom SignalR protocol implementation
- Option A:
- Implement async event handling
- Convert callback system to async-friendly pattern (async generators?)
- Test reconnection logic with async
Phase 5: Testing & Documentation (Week 5-6)
- Convert all tests to async using
pytest-asyncio - Add integration tests for concurrent operations
- Update all examples to use async/await
- Document migration guide for users
- Performance benchmarks (sync vs async)
API Design Decisions
Option 1: Pure Async (Recommended per CLAUDE.md)
# All methods become async
client = AsyncProjectX(api_key, username)
await client.authenticate()
positions = await client.get_positions()Option 2: Dual API (Not recommended due to complexity)
# Both sync and async clients
sync_client = ProjectX(api_key, username)
async_client = AsyncProjectX(api_key, username)Given the CLAUDE.md directive for "No Backward Compatibility" and "Clean Code Priority", Option 1 (Pure Async) is recommended.
Breaking Changes
This refactoring will introduce breaking changes:
- All public methods become
async - Clients must use
async withfor proper cleanup - Event handlers must be async functions
- Example code and integrations need updates
Migration Guide (Draft)
# Old (Sync)
client = ProjectX(api_key, username)
client.authenticate()
positions = client.get_positions()
# New (Async)
async with AsyncProjectX(api_key, username) as client:
await client.authenticate()
positions = await client.get_positions()Technical Considerations
SignalR Compatibility
ProjectX requires SignalR for real-time connections. Options:
- python-signalrcore-async: Check maturity and compatibility
- Async Wrapper: Create async wrapper around sync signalrcore
- Custom Implementation: Use aiohttp with SignalR protocol (complex but most control)
Connection Management
- Use async context managers for resource cleanup
- Implement proper connection pooling for HTTP/2
- Handle WebSocket reconnection in async context
Performance Targets
- Concurrent API calls should show 3-5x throughput improvement
- WebSocket event processing latency < 1ms
- Memory usage should remain comparable to sync version
Dependencies to Add
[project.dependencies]
httpx = ">=0.27.0"
# SignalR async solution (TBD based on research)
[project.optional-dependencies.dev]
pytest-asyncio = ">=0.23.0"
aioresponses = ">=0.7.6" # For mocking async HTTPExample: Async Trading Bot
import asyncio
from project_x_py import AsyncProjectX, create_async_trading_suite
async def trading_bot():
async with AsyncProjectX(api_key, username) as client:
await client.authenticate()
# Create async trading suite
suite = await create_async_trading_suite(
instrument="MGC",
project_x=client,
jwt_token=client.session_token,
account_id=client.account_info.id
)
# Concurrent operations
positions_task = asyncio.create_task(
suite["position_manager"].get_positions()
)
market_data_task = asyncio.create_task(
suite["data_manager"].get_bars("1m", 100)
)
positions, market_data = await asyncio.gather(
positions_task, market_data_task
)
# Real-time event handling
async for tick in suite["data_manager"].stream_ticks():
await process_tick(tick)
async def process_tick(tick):
# Async tick processing
pass
if __name__ == "__main__":
asyncio.run(trading_bot())Timeline
- Total Duration: 5-6 weeks
- Testing Phase: Additional 1 week
- Documentation: Ongoing throughout
Success Criteria
- All tests pass with async implementation
- Performance benchmarks show improvement
- Real-time SignalR connections work reliably
- Clean async API without sync remnants
- Comprehensive documentation and examples
Open Questions
- Which SignalR async library to use?
- Should we provide any sync compatibility layer?
- How to handle existing users during transition?
- Performance benchmarking methodology?
References
- ProjectX SignalR Documentation
- httpx Documentation
- Python Async Best Practices
- SignalR Protocol Specification
Note: This refactoring aligns with the CLAUDE.md directive for "No Backward Compatibility" and "Clean Code Priority" during active development.
Metadata
Metadata
Assignees
Labels
No labels