Skip to content

Commit 71f27f8

Browse files
committed
fix: resolve all mypy type errors in protocols and async method signatures
1 parent 45831c7 commit 71f27f8

File tree

16 files changed

+93
-222
lines changed

16 files changed

+93
-222
lines changed

CLAUDE.md

Lines changed: 9 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44

5+
## Memory Check Protocol:
6+
7+
Before responding to any request, I must:
8+
9+
1. Search memory for user preferences related to the current topic/request
10+
2. Apply saved preferences without asking again
11+
3. Only save NEW preferences, corrections, or special treatments - not tasks or general info
12+
4. Check for topic-specific preferences (e.g., favorite subjects, style preferences, format preferences)
13+
514
## CRITICAL: Testing and Running Examples
615

716
**ALWAYS use `./test.sh` to run tests and examples.** The environment variables are not set globally, but test.sh handles this automatically.
@@ -980,167 +989,3 @@ async with ProjectX.from_env() as client:
980989
- **Fixed**: Eliminated all statistics-related deadlocks with single RW lock per component
981990
- **Changed**: All statistics methods now require `await` for consistency and performance
982991
- **Removed**: Legacy statistics mixins (EnhancedStatsTrackingMixin, StatsTrackingMixin)
983-
984-
### v3.2.1 - Previous Release (2025-08-19)
985-
- **Added**: Complete statistics and analytics system with health monitoring and performance tracking
986-
- **Added**: Fine-grained locking system to prevent deadlocks (replaced single `_stats_lock` with category-specific locks)
987-
- **Added**: Consistent synchronous statistics API across all components for thread-safe access
988-
- **Fixed**: Critical deadlock when OrderManager and StatisticsAggregator accessed locks in opposite order
989-
- **Fixed**: API consistency issues - all `get_memory_stats()` methods now synchronous
990-
- **Enhanced**: StatisticsAggregator with 5-second TTL caching and cross-component metrics
991-
- **Enhanced**: Health scoring algorithm (0-100) with intelligent system monitoring
992-
993-
### v3.2.0 - Previous Release (2025-08-17)
994-
- **Added**: Comprehensive type system overhaul with TypedDict and Protocol definitions
995-
- **Added**: StatsTrackingMixin for error and memory tracking across all managers
996-
- **Added**: Standardized deprecation system with @deprecated decorators
997-
- **Fixed**: Type hierarchy issues between ProjectXBase and ProjectXClientProtocol
998-
- **Fixed**: Response type handling for dict|list union types
999-
- **Improved**: Test coverage with 47 new tests for type system
1000-
- **Improved**: Reduced type errors from 100+ to just 13 edge cases
1001-
1002-
### v3.1.13
1003-
- **Fixed**: Event system data structure mismatches causing order fill detection failures
1004-
- Bracket orders now properly detect fills without 60-second timeouts
1005-
- Event handlers handle both `order_id` and nested `order` object structures
1006-
- ManagedTrade correctly listens to ORDER_FILLED instead of ORDER_MODIFIED
1007-
- **Fixed**: Type annotations for SignalR hub connections
1008-
- Created HubConnection type alias for proper IDE support
1009-
- market_connection and user_connection now have proper types instead of Any
1010-
- **Improved**: Real-time connection stability with circuit breaker pattern
1011-
- **Improved**: Data storage robustness with thread-safety and performance optimizations
1012-
- **Enhanced**: Test coverage increased from 30% to 93% for client module
1013-
- **Fixed**: Multiple asyncio deprecation warnings
1014-
1015-
### v3.1.12
1016-
- **Enhanced**: Significantly improved `01_events_with_on.py` real-time data example
1017-
- Added CSV export functionality with interactive prompts
1018-
- Plotly-based candlestick chart generation
1019-
- Non-blocking user input handling
1020-
- Better bar display formatting and visual indicators
1021-
- Automatic browser opening for generated charts
1022-
1023-
### v3.1.11
1024-
- **Fixed**: ManagedTrade `_get_market_price()` implementation
1025-
- ManagedTrade can now fetch current market prices from data manager
1026-
- Automatic fallback through multiple timeframes (1sec, 15sec, 1min, 5min)
1027-
- Enables risk-managed trades without explicit entry prices
1028-
- Proper integration with TradingSuite's data manager
1029-
1030-
### v3.1.10
1031-
- Minor version bump for internal improvements
1032-
1033-
### v3.1.9
1034-
- **Fixed**: Tick price alignment in real-time data manager
1035-
- All OHLC prices now properly aligned to instrument tick size
1036-
- `get_current_price()` returns tick-aligned values
1037-
- Prevents invalid prices (e.g., $23,927.62 for NQ now snaps to $23,927.50)
1038-
- **Documented**: ProjectX volume data limitation (platform-specific, not full exchange volume)
1039-
1040-
### v3.1.8 - Previous Release
1041-
- **Fixed**: Real-time data processing for E-mini contracts (NQ/ES) that resolve to different symbols
1042-
- **Added**: Bar timer mechanism to create empty bars during low-volume periods
1043-
- **Improved**: Symbol matching to handle contract resolution (e.g., NQENQ)
1044-
- **Enhanced**: Real-time data manager now properly processes all futures contracts
1045-
1046-
### v3.1.7
1047-
- Minor updates and improvements
1048-
- Documentation enhancements
1049-
1050-
### v3.1.6 - Critical Deadlock Fix
1051-
- **Fixed**: Deadlock when calling `suite.data` methods from event handler callbacks (Issue #39)
1052-
- **Improved**: Event emission now non-blocking to prevent handler deadlocks
1053-
- **Enhanced**: Event triggering moved outside lock scope for better concurrency
1054-
- **Added**: Missing asyncio import in data_processing module
1055-
- **Maintained**: Full API compatibility - no breaking changes
1056-
1057-
### v3.1.5 - Enhanced Bar Data Retrieval
1058-
- **Added**: Optional `start_time` and `end_time` parameters to `get_bars()` method
1059-
- **Improved**: Precise time range specification for historical data queries
1060-
- **Enhanced**: Full timezone support with automatic UTC conversion
1061-
- **Maintained**: Complete backward compatibility with existing `days` parameter
1062-
1063-
### v3.1.4 - WebSocket Connection Fix
1064-
- **Fixed**: Critical WebSocket error with missing `_use_batching` attribute
1065-
- **Improved**: Proper mixin initialization in ProjectXRealtimeClient
1066-
- **Enhanced**: More robust real-time connection handling
1067-
1068-
### v3.0.2 - Bug Fixes and Improvements
1069-
- **Order Lifecycle Tracking**: Fixed asyncio concurrency and field reference issues
1070-
- **Order Templates**: Fixed instrument lookup to use cached object
1071-
- **Cleanup Functionality**: Added comprehensive order/position cleanup
1072-
- **Documentation**: Updated all docs to reflect current version
1073-
1074-
### v3.0.1 - Production Ready
1075-
- **Performance Optimizations**: Enhanced connection pooling and caching
1076-
- **Event Bus System**: Unified event handling across all components
1077-
- **Risk Management**: Integrated risk manager with position limits and monitoring
1078-
- **Order Tracking**: Comprehensive order lifecycle tracking and management
1079-
- **Memory Management**: Optimized sliding windows and automatic cleanup
1080-
- **Enhanced Models**: Improved data models with better type safety
1081-
1082-
### v3.0.0 - Major Architecture Improvements
1083-
- **Trading Suite**: Unified trading suite with all managers integrated
1084-
- **Advanced Order Types**: OCO, bracket orders, and position-based orders
1085-
- **Real-time Integration**: Seamless WebSocket data flow across all components
1086-
- **Protocol-based Design**: Type-safe protocols for all major interfaces
1087-
1088-
### v2.0.4 - Package Refactoring
1089-
- Converted monolithic modules to multi-file packages
1090-
- All core modules organized as packages with focused submodules
1091-
- Improved code organization and maintainability
1092-
1093-
### Trading Suite Usage (v3.0.0+)
1094-
```python
1095-
# Complete trading suite with all managers
1096-
from project_x_py import TradingSuite
1097-
1098-
async def main():
1099-
suite = await TradingSuite.create(
1100-
"MNQ",
1101-
timeframes=["1min", "5min"],
1102-
features=["orderbook", "risk_manager"], # Optional features
1103-
initial_days=5
1104-
)
1105-
1106-
# All managers are integrated and ready
1107-
# No need to call start() - already connected
1108-
1109-
# Access individual managers
1110-
order = await suite.orders.place_market_order(
1111-
contract_id=suite.instrument_info.id,
1112-
side=0, # Buy
1113-
size=1
1114-
)
1115-
1116-
position = await suite.positions.get_position("MNQ")
1117-
bars = await suite.data.get_data("1min")
1118-
```
1119-
1120-
### Key Async Examples
1121-
```python
1122-
# Basic usage
1123-
async with ProjectX.from_env() as client:
1124-
await client.authenticate()
1125-
bars = await client.get_bars("MNQ", days=5)
1126-
1127-
# Real-time data with TradingSuite
1128-
async def stream_data():
1129-
suite = await TradingSuite.create(
1130-
"MNQ",
1131-
timeframes=["1min", "5min"]
1132-
)
1133-
1134-
# Register event handlers
1135-
from project_x_py import EventType
1136-
1137-
async def handle_bar(event):
1138-
print(f"New bar: {event.data}")
1139-
1140-
await suite.on(EventType.NEW_BAR, handle_bar)
1141-
1142-
# Data is already streaming
1143-
# Access current data
1144-
current_price = await suite.data.get_current_price()
1145-
bars = await suite.data.get_data("1min")
1146-
```

src/project_x_py/order_manager/position_orders.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ def untrack_order(self: "OrderManagerProtocol", order_id: int) -> None:
355355

356356
logger.debug(f"Untracked order {order_id}")
357357

358-
def get_position_orders(
358+
async def get_position_orders(
359359
self: "OrderManagerProtocol", contract_id: str
360360
) -> dict[str, list[int]]:
361361
"""
@@ -408,7 +408,7 @@ async def cancel_position_orders(
408408
if order_types is None:
409409
order_types = ["entry", "stop", "target"]
410410

411-
position_orders = self.get_position_orders(contract_id)
411+
position_orders = await self.get_position_orders(contract_id)
412412
# Track successful cancellations by type
413413
success_counts = {"entry": 0, "stop": 0, "target": 0, "failed": 0}
414414
error_messages: list[str] = []
@@ -503,7 +503,7 @@ async def update_position_order_sizes(
503503
# TODO: Implement multi-account support using account_id parameter
504504
_ = account_id # Unused for now, reserved for future multi-account support
505505

506-
position_orders = self.get_position_orders(contract_id)
506+
position_orders = await self.get_position_orders(contract_id)
507507
results: dict[str, Any] = {"modified": 0, "failed": 0, "errors": []}
508508

509509
# Update stop and target orders

src/project_x_py/order_manager/tracking.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,9 @@ def _unlink_oco_orders(self: "OrderManagerProtocol", order_id: int) -> int | Non
227227
self.oco_groups.pop(order_id, None)
228228
return None
229229

230-
def get_oco_linked_order(self: "OrderManagerProtocol", order_id: int) -> int | None:
230+
async def get_oco_linked_order(
231+
self: "OrderManagerProtocol", order_id: int
232+
) -> int | None:
231233
"""
232234
Get the order ID linked to the given order in an OCO relationship.
233235
@@ -1343,10 +1345,12 @@ def clear_order_tracking(self: "OrderManagerProtocol") -> None:
13431345
self._completed_orders.clear()
13441346

13451347
# Clear task monitoring data if they exist
1346-
if hasattr(self, "_task_results"):
1347-
self._task_results.clear()
1348-
if hasattr(self, "_cancellation_failures"):
1349-
self._cancellation_failures.clear()
1348+
task_results = getattr(self, "_task_results", None)
1349+
if task_results is not None:
1350+
task_results.clear()
1351+
cancellation_failures = getattr(self, "_cancellation_failures", None)
1352+
if cancellation_failures is not None:
1353+
cancellation_failures.clear()
13501354

13511355
# Reset statistics
13521356
self._memory_stats.update(

src/project_x_py/orderbook/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -487,15 +487,15 @@ async def get_spread_analysis(
487487
return await self.profile.get_spread_analysis(window_minutes)
488488

489489
# Delegate memory methods
490-
def get_memory_stats(self) -> dict[str, Any]:
490+
async def get_memory_stats(self) -> dict[str, Any]:
491491
"""
492492
Get comprehensive memory usage statistics.
493493
494494
Delegates to MemoryManager.get_memory_stats().
495495
See MemoryManager.get_memory_stats() for complete documentation.
496496
"""
497-
# Call the synchronous memory manager method - matches base class signature
498-
stats = self.memory_manager.get_memory_stats()
497+
# Call the async memory manager method
498+
stats = await self.memory_manager.get_memory_stats()
499499
return dict(stats) if stats else {}
500500

501501
async def cleanup(self) -> None:

src/project_x_py/orderbook/base.py

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -890,30 +890,13 @@ async def get_orderbook_memory_usage(self) -> float:
890890

891891
return base_memory + bids_memory + asks_memory + trades_memory + history_memory
892892

893-
def get_memory_stats(self) -> dict[str, Any]:
893+
async def get_memory_stats(self) -> dict[str, Any]:
894894
"""
895-
Get comprehensive memory and statistics (synchronous for backward compatibility).
895+
Get comprehensive memory and statistics.
896896
897897
Returns orderbook-specific statistics compatible with the collector expectations.
898898
"""
899-
import asyncio
900-
901-
# For backward compatibility, run async methods in a new event loop if needed
902-
try:
903-
loop = asyncio.get_event_loop()
904-
if loop.is_running():
905-
# If we're in an async context, we can't use async methods synchronously
906-
# Return basic memory stats only
907-
return self._get_basic_memory_stats()
908-
except RuntimeError:
909-
pass
910-
911-
# If no loop is running, we can create one
912-
try:
913-
return asyncio.run(self._get_comprehensive_stats())
914-
except RuntimeError:
915-
# Fallback to basic stats if async operations fail
916-
return self._get_basic_memory_stats()
899+
return await self._get_comprehensive_stats()
917900

918901
def _get_basic_memory_stats(self) -> dict[str, Any]:
919902
"""Get basic memory stats without async operations."""

src/project_x_py/orderbook/memory.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ async def _cleanup_market_history(self) -> None:
332332
# Delta history - deque handles its own cleanup with maxlen
333333
# No manual cleanup needed for deque with maxlen
334334

335-
def get_memory_stats(self) -> OrderbookStats:
335+
async def get_memory_stats(self) -> OrderbookStats:
336336
"""
337337
Get comprehensive memory usage statistics.
338338
@@ -342,8 +342,6 @@ def get_memory_stats(self) -> OrderbookStats:
342342
debugging memory issues, and validating that the cleanup strategies are working
343343
as expected.
344344
345-
Note: This is a synchronous method that computes stats immediately.
346-
347345
Returns:
348346
Dict containing comprehensive memory statistics including:
349347
orderbook_bids_count: Number of bid price levels

src/project_x_py/position_manager/reporting.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
class PositionReportingMixin:
6464
"""Mixin for statistics, history, and report generation."""
6565

66-
def get_position_statistics(
66+
async def get_position_statistics(
6767
self: "PositionManagerProtocol",
6868
) -> "PositionManagerStats":
6969
"""

src/project_x_py/position_manager/tracking.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,6 @@ async def add_callback(
623623
"add_callback is deprecated. Use TradingSuite.on() with EventType enum instead."
624624
)
625625

626-
def get_position_history_size(self, contract_id: str) -> int:
626+
async def get_position_history_size(self, contract_id: str) -> int:
627627
"""Get the current size of position history for a contract."""
628628
return len(self.position_history.get(contract_id, deque()))

src/project_x_py/realtime_data_manager/core.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -568,10 +568,9 @@ def _init_data_manager_counters(self) -> None:
568568
# These will be tracked using the new BaseStatisticsTracker async methods
569569
# Called during __init__ but actual counter setup happens async
570570

571-
def get_memory_stats(self) -> "RealtimeDataManagerStats":
572-
"""Get comprehensive memory usage statistics (synchronous for backward compatibility)."""
573-
# This method remains synchronous to maintain backward compatibility
574-
# but pulls data from both legacy stats and new statistics system
571+
async def get_memory_stats(self) -> "RealtimeDataManagerStats":
572+
"""Get comprehensive memory usage statistics."""
573+
# Async method for proper I/O handling and new statistics system compatibility
575574

576575
# Update current statistics from data structures
577576
timeframe_stats = {}
@@ -612,7 +611,13 @@ def get_memory_stats(self) -> "RealtimeDataManagerStats":
612611
# Add overflow stats if available
613612
overflow_stats = {}
614613
if hasattr(self, "get_overflow_stats"):
615-
overflow_stats = self.get_overflow_stats()
614+
try:
615+
method = self.get_overflow_stats
616+
if callable(method):
617+
# Method is always async now
618+
overflow_stats = await method()
619+
except Exception:
620+
overflow_stats = {}
616621

617622
# Add lock optimization stats
618623
lock_stats = {}

0 commit comments

Comments
 (0)