Skip to content

Commit c2b246e

Browse files
authored
Merge pull request #31 from TexasCoding/tweaks
Release v3.0.2 - Bug fixes and improvements
2 parents a31f67f + 96c19c3 commit c2b246e

25 files changed

+931
-144
lines changed

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Old implementations are removed when improved
1414
- Clean, modern code architecture is prioritized
1515

16+
## [3.0.2] - 2025-08-08
17+
18+
### Fixed
19+
- **🐛 Order Lifecycle Tracking**: Fixed critical issues in order lifecycle tracking example
20+
- Corrected asyncio.wait() usage by creating tasks instead of passing coroutines
21+
- Fixed instrument lookup - recognized that suite.instrument is already an Instrument object
22+
- Fixed Order field references (use `type` not `orderType`)
23+
- Fixed Position field references (use `size` not `netQuantity`)
24+
- Fixed cancel_order return type handling (returns bool not object)
25+
26+
- **🔧 Order Templates**: Fixed instrument lookup issues
27+
- Removed unnecessary async calls to get_instrument()
28+
- suite.instrument is already resolved after TradingSuite initialization
29+
30+
### Added
31+
- **🧹 Cleanup Functionality**: Comprehensive cleanup for demos and examples
32+
- Automatic cancellation of all open orders at demo completion
33+
- Automatic closing of all open positions
34+
- Cleanup runs in finally block to ensure execution even on errors
35+
- Prevents accumulation of test orders when running examples repeatedly
36+
37+
### Improved
38+
- **📚 Documentation**: Updated all documentation to reflect v3.0.2
39+
- Updated version references throughout
40+
- Added clear documentation of breaking changes
41+
- Improved migration guide clarity
42+
1643
## [3.0.1] - 2025-08-08
1744

1845
### Added

CLAUDE.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

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

5-
## Project Status: v3.0.1 - Production-Ready Async Architecture
5+
## Project Status: v3.0.2 - Production-Ready Async Architecture
66

77
**IMPORTANT**: This project uses a fully asynchronous architecture. All APIs are async-only, optimized for high-performance futures trading.
88

@@ -74,7 +74,7 @@ uv run python -m build # Alternative build command
7474

7575
## Project Architecture
7676

77-
### Core Components (v3.0.1 - Multi-file Packages)
77+
### Core Components (v3.0.2 - Multi-file Packages)
7878

7979
**ProjectX Client (`src/project_x_py/client/`)**
8080
- Main async API client for TopStepX ProjectX Gateway
@@ -278,6 +278,12 @@ async with ProjectX.from_env() as client:
278278

279279
## Recent Changes
280280

281+
### v3.0.2 - Bug Fixes and Improvements
282+
- **Order Lifecycle Tracking**: Fixed asyncio concurrency and field reference issues
283+
- **Order Templates**: Fixed instrument lookup to use cached object
284+
- **Cleanup Functionality**: Added comprehensive order/position cleanup
285+
- **Documentation**: Updated all docs to reflect current version
286+
281287
### v3.0.1 - Production Ready
282288
- **Performance Optimizations**: Enhanced connection pooling and caching
283289
- **Event Bus System**: Unified event handling across all components

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,25 @@ A **high-performance async Python SDK** for the [ProjectX Trading Platform](http
2121

2222
This Python SDK acts as a bridge between your trading strategies and the ProjectX platform, handling all the complex API interactions, data processing, and real-time connectivity.
2323

24-
## 🚀 v3.0.1 - TradingSuite Architecture
24+
## 🚀 v3.0.2 - Production-Ready Trading Suite
2525

26-
**Latest Update (v3.0.1)**: Complete architectural upgrade with TradingSuite for simplified SDK usage, feature flags, and unified event handling.
26+
**Latest Update (v3.0.2)**: Bug fixes and improvements to order lifecycle tracking, comprehensive cleanup functionality, and enhanced error handling.
2727

28-
### What's New in v3.0.1
28+
### What's New in v3.0.2
29+
30+
- **Fixed Order Lifecycle Tracking**: Corrected asyncio concurrency issues and field references
31+
- **Automatic Cleanup**: Added comprehensive cleanup for orders and positions in examples
32+
- **Bug Fixes**: Fixed instrument lookup in order templates and improved error handling
33+
34+
### Key Features from v3.0.1
2935

3036
- **TradingSuite Class**: New unified entry point for simplified SDK usage
3137
- **One-line Initialization**: TradingSuite.create() handles all setup
3238
- **Feature Flags**: Easy enabling of optional components like orderbook and risk manager
3339
- **Context Manager Support**: Automatic cleanup with async with statements
3440
- **Unified Event Handling**: Built-in EventBus for all components
3541

36-
**BREAKING CHANGE**: Version 3.0.1 replaces factory functions with TradingSuite. See migration guide below.
42+
**BREAKING CHANGE**: Version 3.0+ replaces factory functions with TradingSuite. See migration guide below.
3743

3844
### Why Async?
3945

@@ -43,15 +49,15 @@ This Python SDK acts as a bridge between your trading strategies and the Project
4349
- **WebSocket Native**: Perfect for real-time trading applications
4450
- **Modern Python**: Leverages Python 3.12+ async features
4551

46-
### Migration to v3.0.1
52+
### Migration to v3.0+
4753

48-
If you're upgrading from v2.x or v3.0.0, key changes include TradingSuite replacing factories:
54+
If you're upgrading from v2.x, key changes include TradingSuite replacing factories:
4955

5056
```python
51-
# Old (v2.x/v3.0.0)
57+
# Old (v2.x)
5258
suite = await create_initialized_trading_suite(\"MNQ\", client)
5359

54-
# New (v3.0.1)
60+
# New (v3.0+)
5561
suite = await TradingSuite.create(\"MNQ\")
5662
```
5763

@@ -120,7 +126,7 @@ if __name__ == \"__main__\":
120126
asyncio.run(main())
121127
```
122128

123-
### Trading Suite (NEW in v3.0.1)
129+
### Trading Suite (NEW in v3.0+)
124130

125131
The easiest way to get started with a complete trading setup:
126132

SECURITY.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ We currently provide security updates for the following versions:
1010

1111
| Version | Supported |
1212
| ------- | ------------------ |
13-
| 2.0.x | :white_check_mark: |
13+
| 3.0.x | :white_check_mark: |
14+
| 2.0.x | :x: |
1415
| 1.x.x | :x: |
1516

16-
Note: Version 2.0.0 was a complete rewrite with an async-only architecture, and all previous synchronous APIs were removed.
17+
Note: Version 3.0.0 introduced the TradingSuite architecture, replacing all factory functions. Version 2.0.0 was a complete rewrite with an async-only architecture.
1718

1819
## Reporting a Vulnerability
1920

docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
project = "project-x-py"
2424
copyright = "2025, Jeff West"
2525
author = "Jeff West"
26-
release = "3.0.1"
27-
version = "3.0.1"
26+
release = "3.0.2"
27+
version = "3.0.2"
2828

2929
# -- General configuration ---------------------------------------------------
3030

examples/03_position_management.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,16 +104,14 @@ async def display_positions(
104104
try:
105105
# Get current market price
106106
current_price = await suite.data.get_current_price()
107-
if current_price:
108-
# Get instrument info for tick value
109-
instrument_info = await suite.client.get_instrument(
110-
suite.instrument
111-
)
112-
tick_value = instrument_info.tickValue
107+
if current_price and suite.instrument:
108+
# Use the instrument already loaded in suite
109+
instrument_info = suite.instrument
110+
point_value = instrument_info.tickValue / instrument_info.tickSize
113111

114112
# Calculate P&L using position manager's method
115113
pnl_data = await position_manager.calculate_position_pnl(
116-
position, float(current_price), point_value=tick_value
114+
position, float(current_price), point_value=point_value
117115
)
118116
unrealized_pnl = pnl_data["unrealized_pnl"]
119117
except Exception:
@@ -193,18 +191,19 @@ async def monitor_positions(
193191
# Try to calculate real P&L with current prices
194192
if suite and positions:
195193
current_price = await suite.data.get_current_price()
196-
if current_price:
197-
instrument_info = await suite.client.get_instrument(
198-
suite.instrument
194+
if current_price and suite.instrument:
195+
# Use the instrument already loaded in suite
196+
instrument_info = suite.instrument
197+
point_value = (
198+
instrument_info.tickValue / instrument_info.tickSize
199199
)
200-
tick_value = instrument_info.tickValue
201200

202201
for position in positions:
203202
pnl_data = (
204203
await position_manager.calculate_position_pnl(
205204
position,
206205
float(current_price),
207-
point_value=tick_value,
206+
point_value=point_value,
208207
)
209208
)
210209
total_pnl += pnl_data["unrealized_pnl"]

examples/04_realtime_data.py

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
TradingSuite,
3535
setup_logging,
3636
)
37-
from project_x_py.types.protocols import RealtimeDataManagerProtocol
3837

3938
if TYPE_CHECKING:
4039
from project_x_py.realtime_data_manager import RealtimeDataManager
@@ -46,16 +45,18 @@ async def display_current_prices(data_manager: "RealtimeDataManager") -> None:
4645

4746
current_price = await data_manager.get_current_price()
4847
if current_price:
49-
print(f" Current Price: ${current_price:.2f}")
48+
print(f" Live Price: ${current_price:.2f}")
5049
else:
51-
print(" Current Price: Not available")
50+
print(" Live Price: Not available")
5251

53-
# Get multi-timeframe data asynchronously - get 1 bar from each timeframe
52+
# Get multi-timeframe data asynchronously - get latest bars
5453
timeframes = ["15sec", "1min", "5min", "15min", "1hr"]
5554
mtf_tasks: list[Coroutine[Any, Any, pl.DataFrame | None]] = []
5655

5756
for tf in timeframes:
58-
mtf_tasks.append(data_manager.get_data(tf, bars=1))
57+
mtf_tasks.append(
58+
data_manager.get_data(tf)
59+
) # Get all data, we'll take the last bar
5960

6061
# Get data from all timeframes concurrently
6162
mtf_results = await asyncio.gather(*mtf_tasks, return_exceptions=True)
@@ -70,19 +71,26 @@ async def display_current_prices(data_manager: "RealtimeDataManager") -> None:
7071
continue
7172

7273
if data.is_empty():
73-
print(f" {timeframe:>6}: No data")
74+
print(f" {timeframe:>6}: Building bars...")
7475
continue
7576

77+
# Get the last complete bar
7678
latest_bar = data.tail(1)
7779
for row in latest_bar.iter_rows(named=True):
7880
timestamp = row["timestamp"]
7981
close = row["close"]
8082
volume = row["volume"]
83+
# Format timestamp more concisely
84+
time_str = (
85+
timestamp.strftime("%H:%M:%S")
86+
if hasattr(timestamp, "strftime")
87+
else str(timestamp)
88+
)
8189
print(
82-
f" {timeframe:>6}: ${close:8.2f} @ {timestamp} (Vol: {volume:,})"
90+
f" {timeframe:>6}: ${close:8.2f} @ {time_str} (Vol: {volume:,})"
8391
)
8492
else:
85-
print(f" {timeframe:>6}: No data")
93+
print(f" {timeframe:>6}: Initializing...")
8694

8795

8896
async def display_memory_stats(data_manager: "RealtimeDataManager") -> None:
@@ -91,17 +99,28 @@ async def display_memory_stats(data_manager: "RealtimeDataManager") -> None:
9199
# get_memory_stats is synchronous in async data manager
92100
stats = data_manager.get_memory_stats()
93101
print("\n💾 Memory Statistics:")
94-
print(f" Total Bars: {stats.get('total_bars', 0):,}")
102+
print(f" Total Bars Stored: {stats.get('total_bars_stored', 0):,}")
95103
print(f" Ticks Processed: {stats.get('ticks_processed', 0):,}")
96-
print(f" Bars Cleaned: {stats.get('bars_cleaned', 0):,}")
97-
print(f" Tick Buffer Size: {stats.get('tick_buffer_size', 0):,}")
98-
99-
# Show per-timeframe breakdown
100-
breakdown = stats.get("timeframe_breakdown", {})
101-
if breakdown:
102-
print(" Timeframe Breakdown:")
103-
for tf, count in breakdown.items():
104-
print(f" {tf}: {count:,} bars")
104+
print(f" Quotes Processed: {stats.get('quotes_processed', 0):,}")
105+
print(f" Trades Processed: {stats.get('trades_processed', 0):,}")
106+
107+
# Show per-timeframe breakdown (Note: this key doesn't exist in current implementation)
108+
# Will need to calculate it manually from the data
109+
print(" Bars per Timeframe:")
110+
for tf in data_manager.timeframes:
111+
if tf in data_manager.data:
112+
count = len(data_manager.data[tf])
113+
if count > 0:
114+
print(f" {tf}: {count:,} bars")
115+
116+
# Also show validation status for more insight
117+
validation = data_manager.get_realtime_validation_status()
118+
if validation.get("is_running"):
119+
print(
120+
f" Feed Active: ✅ (Processing {validation.get('instrument', 'N/A')})"
121+
)
122+
else:
123+
print(" Feed Active: ❌")
105124

106125
except Exception as e:
107126
print(f" ❌ Memory stats error: {e}")
@@ -113,11 +132,11 @@ async def display_system_statistics(data_manager: "RealtimeDataManager") -> None
113132
# Use validation status instead of get_statistics (which doesn't exist)
114133
stats = data_manager.get_realtime_validation_status()
115134
print("\n📈 System Status:")
116-
print(f" Instrument: {getattr(data_manager, 'instrument', 'Unknown')}")
117-
print(f" Contract ID: {getattr(data_manager, 'contract_id', 'Unknown')}")
118-
print(f" Real-time Enabled: {stats.get('realtime_enabled', False)}")
119-
print(f" Connection Valid: {stats.get('connection_valid', False)}")
120-
print(f" Data Valid: {stats.get('data_valid', False)}")
135+
print(f" Instrument: {stats.get('instrument', 'Unknown')}")
136+
print(f" Contract ID: {stats.get('contract_id', 'Unknown')}")
137+
print(f" Real-time Feed Active: {stats.get('is_running', False)}")
138+
print(f" Ticks Processed: {stats.get('ticks_processed', 0):,}")
139+
print(f" Bars Cleaned: {stats.get('bars_cleaned', 0):,}")
121140

122141
# Show data status per timeframe
123142
print(" Timeframe Status:")
@@ -173,14 +192,17 @@ async def demonstrate_historical_analysis(data_manager: "RealtimeDataManager") -
173192
print(f" ❌ Analysis error: {e}")
174193

175194

176-
async def new_bar_callback(data: dict[str, Any]) -> None:
195+
async def new_bar_callback(event: Any) -> None:
177196
"""Handle new bar creation asynchronously."""
178197
timestamp = datetime.now().strftime("%H:%M:%S")
179-
timeframe = data["timeframe"]
180-
bar = data["data"]
181-
print(
182-
f"📊 [{timestamp}] New {timeframe} Bar: ${bar['close']:.2f} (Vol: {bar['volume']:,})"
183-
)
198+
# Extract data from the Event object
199+
data = event.data if hasattr(event, "data") else event
200+
timeframe = data.get("timeframe", "unknown")
201+
bar = data.get("data", {})
202+
if bar and "close" in bar and "volume" in bar:
203+
print(
204+
f"📊 [{timestamp}] New {timeframe} Bar: ${bar['close']:.2f} (Vol: {bar['volume']:,})"
205+
)
184206

185207

186208
async def main() -> bool:
@@ -221,7 +243,6 @@ async def main() -> bool:
221243

222244
# Components are now accessed as attributes
223245
data_manager = suite.data
224-
realtime_client = suite.realtime
225246

226247
print("\n✅ All components connected and subscribed:")
227248
print(" - Real-time client connected")

0 commit comments

Comments
 (0)