Skip to content

Commit 548eeed

Browse files
committed
feat(async): add API compatibility improvements for sync migration
- Add 'live' parameter to AsyncProjectX.get_instrument() for sync compatibility - Create migration plan documentation (sync_to_async_migration_plan.md) - Document migration gaps and action items (async_migration_gaps.md) - Identify missing methods and API differences - All async examples verified working
1 parent 54e2841 commit 548eeed

26 files changed

+1179
-57
lines changed

async_migration_gaps.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Async Migration Gaps and Action Items
2+
3+
## Critical Gaps to Address
4+
5+
### 1. Missing Methods in AsyncProjectX
6+
7+
#### High Priority (Core Functionality):
8+
- [ ] **`get_account_info()` property**: Sync version has this as a property/method. Async version stores it but doesn't expose it as a property
9+
- **Action**: Add `@property` for `account_info` in AsyncProjectX
10+
11+
- [ ] **`get_session_token()` property**: Sync exposes JWT token retrieval
12+
- **Action**: Add `@property` for `session_token` in AsyncProjectX
13+
14+
#### Low Priority (Nice to Have):
15+
- [ ] **`test_contract_selection()`**: Testing utility
16+
- **Action**: Consider if needed in async version
17+
18+
### 2. Method Signature Differences
19+
20+
#### Must Fix:
21+
- [ ] **`get_instrument()`**: Async version missing `live` parameter
22+
- **Action**: Add `live: bool = False` parameter to async version
23+
24+
- [ ] **`list_accounts()`**: Return type mismatch
25+
- **Sync**: Returns `list[dict]`
26+
- **Async**: Returns `list[Account]`
27+
- **Action**: Verify if this is intentional improvement or needs alignment
28+
29+
### 3. Method Naming Inconsistencies
30+
31+
- [ ] **`get_data()` vs `get_bars()`**: Same functionality, different names
32+
- **Action**: Consider adding `get_data()` as alias for backwards compatibility
33+
34+
### 4. Authentication Pattern Differences
35+
36+
The async version requires explicit `authenticate()` call while sync does it automatically.
37+
- **Action**: Document this clearly in migration guide
38+
39+
## Components Verification Status
40+
41+
### ✅ Fully Verified Components:
42+
1. **Examples**: All 9 examples have working async versions
43+
2. **Factory Functions**: All have async equivalents in `__init__.py`
44+
45+
### 🔄 Partially Verified Components:
46+
1. **AsyncProjectX**: Missing some convenience methods/properties
47+
2. **AsyncOrderManager**: Needs method-by-method verification
48+
3. **AsyncPositionManager**: Needs method-by-method verification
49+
4. **AsyncRealtimeDataManager**: Needs method-by-method verification
50+
5. **AsyncOrderBook**: Refactored into module structure, needs verification
51+
52+
### ❓ Not Yet Verified:
53+
1. Unit test coverage comparison
54+
2. Performance benchmarks
55+
3. Real-world usage patterns
56+
57+
## Immediate Action Items
58+
59+
1. **Add missing properties to AsyncProjectX**:
60+
```python
61+
@property
62+
def account_info(self) -> Account | None:
63+
return self._account_info
64+
65+
@property
66+
def session_token(self) -> str:
67+
return self._session_token
68+
```
69+
70+
2. **Fix `get_instrument()` signature**:
71+
```python
72+
async def get_instrument(self, symbol: str, live: bool = False) -> Instrument:
73+
```
74+
75+
3. **Create detailed method comparison for remaining components**:
76+
- AsyncOrderManager vs OrderManager
77+
- AsyncPositionManager vs PositionManager
78+
- AsyncRealtimeDataManager vs ProjectXRealtimeDataManager
79+
- AsyncOrderBook vs OrderBook
80+
81+
## Migration Strategy Recommendations
82+
83+
1. **Phase 1**: Fix critical gaps (properties, method signatures)
84+
2. **Phase 2**: Add deprecation warnings to sync versions
85+
3. **Phase 3**: Create comprehensive migration guide with examples
86+
4. **Phase 4**: Release v2.0.0 with async-only support
87+
88+
## Backwards Compatibility Options
89+
90+
Consider creating sync wrappers for critical methods:
91+
```python
92+
def get_data_sync(client: AsyncProjectX, *args, **kwargs):
93+
"""Sync wrapper for backwards compatibility"""
94+
import asyncio
95+
return asyncio.run(client.get_bars(*args, **kwargs))
96+
```
97+
98+
## Testing Requirements
99+
100+
Before removing sync versions:
101+
1. Run all examples with both sync and async
102+
2. Compare outputs for consistency
103+
3. Benchmark performance differences
104+
4. Test error handling scenarios
105+
5. Verify real-time data handling
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Async OrderBook Remaining Issues
2+
3+
## Summary of Fixed Issues ✅
4+
1. **get_liquidity_levels** - Now returns lists instead of DataFrames
5+
2. **get_order_type_statistics** - Made non-async
6+
3. **get_recent_trades** - Returns properly formatted list
7+
4. **get_price_level_history** - Added method with correct signature
8+
5. **Timezone comparisons** - Fixed in cumulative_delta, trade_flow_summary, and volume_profile
9+
10+
## Remaining Issues to Fix 🔧
11+
12+
### 1. Iceberg Detection Not Working
13+
**Issue**: Always returns "No potential iceberg orders detected"
14+
**Root Cause**: The async version uses a simplified algorithm compared to sync version
15+
**Sync Version Features**:
16+
- Builds DataFrame from price level history for statistical analysis
17+
- Uses groupby operations for aggregation
18+
- Calculates sophisticated confidence scores
19+
- Cross-references with trade data
20+
- Has configurable parameters for min_total_volume, statistical_confidence
21+
22+
**Solution**: Port the sync version's advanced detection logic
23+
24+
### 2. Order Clusters Detection Too Sensitive
25+
**Issue**: Finding 66 clusters (33 bid, 33 ask) which seems excessive
26+
**Root Cause**: The clustering algorithm is too simple - it just groups prices within tolerance
27+
**Sync Version Features**:
28+
- Uses price level history for clustering
29+
- Calculates cluster strength based on persistence and volume
30+
- Enhances clusters with current orderbook data
31+
- Has better tolerance calculation (3x tick size)
32+
33+
**Solution**: Implement more sophisticated clustering that considers volume and persistence
34+
35+
### 3. Volume Profile Shows Zero POC
36+
**Issue**: Point of Control (POC) shows $0.00 despite having trades
37+
**Possible Cause**: The bucketing logic might not be working correctly
38+
**Solution**: Debug the price bucketing algorithm
39+
40+
### 4. Missing Cross-References
41+
The sync version has several helper methods that the async version lacks:
42+
- `_cross_reference_with_trades`
43+
- `_find_clusters_from_history`
44+
- `_enhance_clusters_with_current_data`
45+
46+
## Recommendations
47+
48+
1. **Priority 1**: Fix iceberg detection by porting the sync version's algorithm
49+
2. **Priority 2**: Improve cluster detection to reduce false positives
50+
3. **Priority 3**: Fix volume profile POC calculation
51+
4. **Priority 4**: Consider implementing the sync version's `get_liquidity_levels` which analyzes persistent liquidity using price level history
52+
53+
The async version needs to match the sync version's sophisticated market microstructure analysis capabilities for production use.

benchmarks/async_vs_sync_benchmark.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
import asyncio
1616
import time
1717
from concurrent.futures import ThreadPoolExecutor
18-
from datetime import datetime
1918
from statistics import mean, stdev
20-
from typing import Any, Dict, List
2119

2220
from project_x_py import (
2321
AsyncProjectX,
@@ -31,12 +29,12 @@ class BenchmarkResults:
3129

3230
def __init__(self, name: str):
3331
self.name = name
34-
self.times: List[float] = []
32+
self.times: list[float] = []
3533

3634
def add_time(self, duration: float):
3735
self.times.append(duration)
3836

39-
def get_stats(self) -> Dict[str, float]:
37+
def get_stats(self) -> dict[str, float]:
4038
if not self.times:
4139
return {"mean": 0, "stdev": 0, "min": 0, "max": 0}
4240

clean_iceberg_method.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env python3
2+
"""Clean up the broken iceberg method."""
3+
4+
# Read the file
5+
with open("src/project_x_py/async_orderbook.py") as f:
6+
lines = f.readlines()
7+
8+
# Find the broken part starting at line 1001
9+
# Remove everything from line 1001 to the next valid method
10+
output_lines = []
11+
skip = False
12+
for i, line in enumerate(lines):
13+
if i == 1000: # Line 1001 (0-indexed)
14+
skip = True
15+
elif skip and line.strip().startswith("async def add_callback"):
16+
skip = False
17+
18+
if not skip:
19+
output_lines.append(line)
20+
21+
# Write back
22+
with open("src/project_x_py/async_orderbook.py", "w") as f:
23+
f.writelines(output_lines)
24+
25+
print("✅ Cleaned up broken iceberg method implementation")

examples/async_06_multi_timeframe_strategy.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ async def generate_trading_signal(self):
194194
orderbook = analysis.get("orderbook")
195195

196196
# Check if we have all required data
197-
if not all([longterm, medium, shortterm]):
197+
if not longterm or not medium or not shortterm:
198198
return None
199199

200200
# Strategy logic: All timeframes must align
@@ -210,14 +210,17 @@ async def generate_trading_signal(self):
210210
if medium["momentum"] == "strong":
211211
confidence = min(confidence * 1.2, 100)
212212

213-
elif shortterm["signal"] == "sell":
214-
if longterm["trend"] == "bearish" and medium["trend"] == "bearish":
215-
signal = "SELL"
216-
confidence = min(longterm["strength"] * 100, 100)
213+
elif (
214+
shortterm["signal"] == "sell"
215+
and longterm["trend"] == "bearish"
216+
and medium["trend"] == "bearish"
217+
):
218+
signal = "SELL"
219+
confidence = min(longterm["strength"] * 100, 100)
217220

218-
# Boost confidence if momentum is strong
219-
if medium["momentum"] == "weak":
220-
confidence = min(confidence * 1.2, 100)
221+
# Boost confidence if momentum is strong
222+
if medium["momentum"] == "weak":
223+
confidence = min(confidence * 1.2, 100)
221224

222225
if signal:
223226
self.signal_count += 1
@@ -395,6 +398,11 @@ def signal_handler(signum, frame):
395398
# Create async client
396399
async with AsyncProjectX.from_env() as client:
397400
await client.authenticate()
401+
402+
if client.account_info is None:
403+
print("❌ No account info found")
404+
return
405+
398406
print(f"✅ Connected as: {client.account_info.name}")
399407

400408
# Create trading suite with all components

examples/async_factory_functions_demo.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
"""
77

88
import asyncio
9-
import json
10-
from datetime import datetime
119

1210
from project_x_py import (
1311
create_async_client,

examples/async_integrated_trading_suite.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"""
77

88
import asyncio
9-
import json
109
from datetime import datetime
1110

1211
from project_x_py import (
@@ -177,7 +176,7 @@ async def main():
177176
print(
178177
f" Connection Errors: {final_stats['connection_errors'] - initial_stats['connection_errors']}"
179178
)
180-
print(f" Managers Sharing Connection: 4 (Position, Order, Data, OrderBook)")
179+
print(" Managers Sharing Connection: 4 (Position, Order, Data, OrderBook)")
181180

182181
# Clean up
183182
print("\n🧹 Cleaning up...")

examples/async_order_manager_usage.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
"""
77

88
import asyncio
9-
import os
10-
from decimal import Decimal
119

1210
from project_x_py import AsyncOrderManager, AsyncProjectX
1311

@@ -56,7 +54,7 @@ async def main():
5654
take_profit_offset=20.0, # 20 points above entry
5755
)
5856
if bracket and bracket.success:
59-
print(f"✅ Bracket order placed:")
57+
print("✅ Bracket order placed:")
6058
print(f" Entry: {bracket.entry_order_id}")
6159
print(f" Stop Loss: {bracket.stop_order_id}")
6260
print(f" Take Profit: {bracket.target_order_id}")
@@ -84,7 +82,7 @@ async def main():
8482
print(f"\n❌ Cancelling order {open_orders[1].id}...")
8583
success = await order_manager.cancel_order(open_orders[1].id)
8684
if success:
87-
print(f"✅ Order cancelled")
85+
print("✅ Order cancelled")
8886

8987
# 7. Display statistics
9088
stats = order_manager.get_stats()

examples/async_position_manager_usage.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
"""
77

88
import asyncio
9-
import os
10-
from decimal import Decimal
119

1210
from project_x_py import AsyncPositionManager, AsyncProjectX
1311

examples/async_realtime_client_usage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ async def main():
161161

162162
# Show final stats
163163
final_stats = realtime_client.get_stats()
164-
print(f"\n📊 Final Statistics:")
164+
print("\n📊 Final Statistics:")
165165
print(f" Events Received: {final_stats['events_received']}")
166166
print(f" Connection Errors: {final_stats['connection_errors']}")
167167

0 commit comments

Comments
 (0)