Skip to content

Commit 2efd24d

Browse files
TexasCodingclaude
andcommitted
feat: replace magic numbers with enums throughout codebase
- Replaced all magic numbers with proper enum values from types/trading.py - OrderSide enum: BUY=0, SELL=1 - OrderType enum: LIMIT=1, MARKET=2, STOP=4, etc. - OrderStatus enum: OPEN=1, FILLED=2, CANCELLED=3, etc. - PositionType enum: LONG=1, SHORT=2 - TradeLogType enum: BUY=0, SELL=1 - Fixed test failures from previous refactoring: - Updated retry count expectations in HTTP client tests - Added exception conversion for connection errors - Fixed standardized error message expectations - Updated order cancellation/modification tests to expect exceptions - Fixed missing runtime imports of PositionType enum - Corrected enum value expectations in type tests - Updated CHANGELOG.md for version 2.0.6 - All 299 tests now pass successfully This improves code readability, type safety, and prevents invalid numeric values. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent f2b97a7 commit 2efd24d

File tree

22 files changed

+143
-74
lines changed

22 files changed

+143
-74
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,31 @@ 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+
## [2.0.6] - 2025-08-03
17+
18+
### Changed
19+
- **🔢 Enum Usage**: Replaced magic numbers with proper enum values throughout codebase
20+
- All order side values now use `OrderSide` enum (BUY=0, SELL=1)
21+
- All order type values now use `OrderType` enum (LIMIT=1, MARKET=2, STOP=4, etc.)
22+
- All order status values now use `OrderStatus` enum (OPEN=1, FILLED=2, CANCELLED=3, etc.)
23+
- All position type values now use `PositionType` enum (LONG=1, SHORT=2)
24+
- Trade log types now use `TradeLogType` enum (BUY=0, SELL=1)
25+
- Improved code readability and maintainability
26+
- All enum values match ProjectX Gateway documentation
27+
28+
### Fixed
29+
- **🧪 Test Suite**: Fixed all test failures from recent refactoring
30+
- HTTP client retry logic tests now expect correct retry counts
31+
- Connection/timeout errors properly converted to `ProjectXConnectionError`
32+
- Order cancellation and modification tests updated to expect exceptions
33+
- Market data tests updated for standardized error messages
34+
- Type tests updated with correct enum values
35+
36+
### Improved
37+
- **📖 Code Documentation**: Updated inline documentation to reference enums
38+
- **🔍 Type Safety**: Better type checking with enum usage
39+
- **🐛 Bug Prevention**: Enum usage prevents invalid numeric values
40+
1641
## [2.0.5] - 2025-08-03
1742

1843
### Added

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 = "2.0.5"
27-
version = "2.0.5"
26+
release = "2.0.6"
27+
version = "2.0.6"
2828

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

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "project-x-py"
3-
version = "2.0.5"
3+
version = "2.0.6"
44
description = "High-performance Python SDK for futures trading with real-time WebSocket data, technical indicators, order management, and market depth analysis"
55
readme = "README.md"
66
license = { text = "MIT" }

src/project_x_py/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797

9898
from project_x_py.client.base import ProjectXBase
9999

100-
__version__ = "2.0.5"
100+
__version__ = "2.0.6"
101101
__author__ = "TexasCoding"
102102

103103
# Core client classes - renamed from Async* to standard names

src/project_x_py/client/auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ async def authenticate(self: "ProjectXClientProtocol") -> None:
139139
>>> print(f"Authenticated as {client.account_info.username}")
140140
>>> print(f"Using account: {client.account_info.name}")
141141
"""
142-
logger.info(LogMessages.AUTH_START, extra={"username": self.username})
142+
logger.debug(LogMessages.AUTH_START, extra={"username": self.username})
143143

144144
# Authenticate and get token
145145
auth_data = {
@@ -210,7 +210,7 @@ async def authenticate(self: "ProjectXClientProtocol") -> None:
210210

211211
self.account_info = selected_account
212212
self._authenticated = True
213-
logger.info(
213+
logger.debug(
214214
LogMessages.AUTH_SUCCESS,
215215
extra={
216216
"account_name": selected_account.name,

src/project_x_py/client/http.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ async def main():
4747

4848
from project_x_py.exceptions import (
4949
ProjectXAuthenticationError,
50+
ProjectXConnectionError,
5051
ProjectXDataError,
5152
ProjectXError,
5253
ProjectXRateLimitError,
@@ -178,7 +179,7 @@ async def _make_request(
178179
has_data=data is not None,
179180
has_params=params is not None,
180181
):
181-
logger.info(
182+
logger.debug(
182183
LogMessages.API_REQUEST, extra={"method": method, "endpoint": endpoint}
183184
)
184185

@@ -197,13 +198,16 @@ async def _make_request(
197198
self.api_call_count += 1
198199
start_time = time.time()
199200

200-
response = await client.request(
201-
method=method,
202-
url=url,
203-
json=data,
204-
params=params,
205-
headers=request_headers,
206-
)
201+
try:
202+
response = await client.request(
203+
method=method,
204+
url=url,
205+
json=data,
206+
params=params,
207+
headers=request_headers,
208+
)
209+
except (httpx.ConnectError, httpx.TimeoutException) as e:
210+
raise ProjectXConnectionError(str(e)) from e
207211

208212
# Log API call
209213
log_api_call(

src/project_x_py/indicators/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@
190190
)
191191

192192
# Version info
193-
__version__ = "2.0.5"
193+
__version__ = "2.0.6"
194194
__author__ = "TexasCoding"
195195

196196

src/project_x_py/models.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@
5353
contractId="CON.F.US.MGC.M25",
5454
creationTimestamp="2024-01-01T10:00:00Z",
5555
updateTimestamp="2024-01-01T10:00:05Z",
56-
status=1, # Open
57-
type=1, # Limit
58-
side=0, # Bid
56+
status=OrderStatus.OPEN,
57+
type=OrderType.LIMIT,
58+
side=OrderSide.BUY,
5959
size=5,
6060
limitPrice=2050.0,
6161
)
@@ -66,7 +66,7 @@
6666
accountId=1001,
6767
contractId="CON.F.US.MGC.M25",
6868
creationTimestamp="2024-01-01T10:00:00Z",
69-
type=1, # Long
69+
type=PositionType.LONG,
7070
size=5,
7171
averagePrice=2050.0,
7272
)
@@ -265,7 +265,8 @@ class Position:
265265
accountId (int): Account holding the position
266266
contractId (str): Contract of the position
267267
creationTimestamp (str): When the position was opened (ISO format)
268-
type (int): Position type code (1=LONG, 2=SHORT)
268+
type (int): Position type code (PositionType enum):
269+
0=UNDEFINED, 1=LONG, 2=SHORT
269270
size (int): Position size (number of contracts, always positive)
270271
averagePrice (float): Average entry price of the position
271272
@@ -274,7 +275,7 @@ class Position:
274275
For P&L calculations, use PositionManager.calculate_position_pnl() method.
275276
276277
Example:
277-
>>> direction = "LONG" if position.type == 1 else "SHORT"
278+
>>> direction = "LONG" if position.type == PositionType.LONG else "SHORT"
278279
>>> print(
279280
... f"{direction} {position.size} {position.contractId} @ ${position.averagePrice}"
280281
... )

src/project_x_py/order_manager/core.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from project_x_py.exceptions import ProjectXOrderError
4242
from project_x_py.models import Order, OrderPlaceResponse
4343
from project_x_py.types import OrderStats
44+
from project_x_py.types.trading import OrderStatus
4445
from project_x_py.utils import (
4546
ErrorMessages,
4647
LogContext,
@@ -441,14 +442,14 @@ async def is_order_filled(self, order_id: str | int) -> bool:
441442
async with self.order_lock:
442443
status = self.order_status_cache.get(order_id_str)
443444
if status is not None:
444-
return status == 2 # 2 = Filled
445+
return status == OrderStatus.FILLED
445446

446447
if attempt < 2: # Don't sleep on last attempt
447448
await asyncio.sleep(0.2) # Brief wait for real-time update
448449

449450
# Fallback to API check
450451
order = await self.get_order_by_id(int(order_id))
451-
return order is not None and order.status == 2 # 2 = Filled
452+
return order is not None and order.status == OrderStatus.FILLED
452453

453454
async def get_order_by_id(self, order_id: int) -> Order | None:
454455
"""
@@ -520,8 +521,8 @@ async def cancel_order(self, order_id: int, account_id: int | None = None) -> bo
520521
if success:
521522
# Update cache
522523
if str(order_id) in self.tracked_orders:
523-
self.tracked_orders[str(order_id)]["status"] = 3 # Cancelled
524-
self.order_status_cache[str(order_id)] = 3
524+
self.tracked_orders[str(order_id)]["status"] = OrderStatus.CANCELLED
525+
self.order_status_cache[str(order_id)] = OrderStatus.CANCELLED
525526

526527
self.stats["orders_cancelled"] = (
527528
self.stats.get("orders_cancelled", 0) + 1

src/project_x_py/order_manager/order_types.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from typing import TYPE_CHECKING
4646

4747
from project_x_py.models import OrderPlaceResponse
48+
from project_x_py.types.trading import OrderType
4849

4950
if TYPE_CHECKING:
5051
from project_x_py.types import OrderManagerProtocol
@@ -88,7 +89,7 @@ async def place_market_order(
8889
contract_id=contract_id,
8990
side=side,
9091
size=size,
91-
order_type=2, # Market
92+
order_type=OrderType.MARKET,
9293
account_id=account_id,
9394
)
9495

@@ -120,7 +121,7 @@ async def place_limit_order(
120121
contract_id=contract_id,
121122
side=side,
122123
size=size,
123-
order_type=1, # Limit
124+
order_type=OrderType.LIMIT,
124125
limit_price=limit_price,
125126
account_id=account_id,
126127
)
@@ -154,7 +155,7 @@ async def place_stop_order(
154155
contract_id=contract_id,
155156
side=side,
156157
size=size,
157-
order_type=4, # Stop
158+
order_type=OrderType.STOP,
158159
stop_price=stop_price,
159160
account_id=account_id,
160161
)
@@ -187,7 +188,7 @@ async def place_trailing_stop_order(
187188
"""
188189
return await self.place_order(
189190
contract_id=contract_id,
190-
order_type=5, # Trailing stop order
191+
order_type=OrderType.TRAILING_STOP,
191192
side=side,
192193
size=size,
193194
trail_price=trail_price,

0 commit comments

Comments
 (0)