Skip to content

Commit 3edd369

Browse files
committed
refactor(trading): Improve TradingSuite stability and configuration
1 parent d243c76 commit 3edd369

File tree

2 files changed

+96
-39
lines changed

2 files changed

+96
-39
lines changed

src/project_x_py/risk_manager/core.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ def __init__(
8383
self._current_risk = Decimal("0")
8484
self._max_drawdown = Decimal("0")
8585

86+
def set_position_manager(self, position_manager: PositionManagerProtocol) -> None:
87+
"""Set the position manager after initialization to resolve circular dependency."""
88+
self.positions = position_manager
89+
self.position_manager = position_manager
90+
8691
async def calculate_position_size(
8792
self,
8893
entry_price: float,

src/project_x_py/trading_suite.py

Lines changed: 91 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from enum import Enum
3939
from pathlib import Path
4040
from types import TracebackType
41-
from typing import Any
41+
from typing import Any, cast
4242

4343
import orjson
4444
import yaml
@@ -60,6 +60,7 @@
6060
OrderManagerConfig,
6161
PositionManagerConfig,
6262
)
63+
from project_x_py.types.protocols import ProjectXClientProtocol
6364
from project_x_py.types.stats_types import ComponentStats, TradingSuiteStats
6465
from project_x_py.utils import ProjectXLogger
6566

@@ -87,16 +88,33 @@ def __init__(
8788
initial_days: int = 5,
8889
auto_connect: bool = True,
8990
timezone: str = "America/Chicago",
91+
order_manager_config: OrderManagerConfig | None = None,
92+
position_manager_config: PositionManagerConfig | None = None,
93+
data_manager_config: DataManagerConfig | None = None,
94+
orderbook_config: OrderbookConfig | None = None,
95+
risk_config: RiskConfig | None = None,
9096
):
9197
self.instrument = instrument
9298
self.timeframes = timeframes or ["5min"]
9399
self.features = features or []
94100
self.initial_days = initial_days
95101
self.auto_connect = auto_connect
96102
self.timezone = timezone
103+
self.order_manager_config = order_manager_config
104+
self.position_manager_config = position_manager_config
105+
self.data_manager_config = data_manager_config
106+
self.orderbook_config = orderbook_config
107+
self.risk_config = risk_config
97108

98109
def get_order_manager_config(self) -> OrderManagerConfig:
99-
"""Get configuration for OrderManager."""
110+
"""
111+
Get configuration for OrderManager.
112+
113+
Returns:
114+
OrderManagerConfig: The configuration for the OrderManager.
115+
"""
116+
if self.order_manager_config:
117+
return self.order_manager_config
100118
return {
101119
"enable_bracket_orders": Features.RISK_MANAGER in self.features,
102120
"enable_trailing_stops": True,
@@ -105,7 +123,14 @@ def get_order_manager_config(self) -> OrderManagerConfig:
105123
}
106124

107125
def get_position_manager_config(self) -> PositionManagerConfig:
108-
"""Get configuration for PositionManager."""
126+
"""
127+
Get configuration for PositionManager.
128+
129+
Returns:
130+
PositionManagerConfig: The configuration for the PositionManager.
131+
"""
132+
if self.position_manager_config:
133+
return self.position_manager_config
109134
return {
110135
"enable_risk_monitoring": Features.RISK_MANAGER in self.features,
111136
"enable_correlation_analysis": Features.PERFORMANCE_ANALYTICS
@@ -114,7 +139,14 @@ def get_position_manager_config(self) -> PositionManagerConfig:
114139
}
115140

116141
def get_data_manager_config(self) -> DataManagerConfig:
117-
"""Get configuration for RealtimeDataManager."""
142+
"""
143+
Get configuration for RealtimeDataManager.
144+
145+
Returns:
146+
DataManagerConfig: The configuration for the RealtimeDataManager.
147+
"""
148+
if self.data_manager_config:
149+
return self.data_manager_config
118150
return {
119151
"max_bars_per_timeframe": 1000,
120152
"enable_tick_data": True,
@@ -124,7 +156,14 @@ def get_data_manager_config(self) -> DataManagerConfig:
124156
}
125157

126158
def get_orderbook_config(self) -> OrderbookConfig:
127-
"""Get configuration for OrderBook."""
159+
"""
160+
Get configuration for OrderBook.
161+
162+
Returns:
163+
OrderbookConfig: The configuration for the OrderBook.
164+
"""
165+
if self.orderbook_config:
166+
return self.orderbook_config
128167
return {
129168
"max_depth_levels": 100,
130169
"max_trade_history": 1000,
@@ -133,7 +172,14 @@ def get_orderbook_config(self) -> OrderbookConfig:
133172
}
134173

135174
def get_risk_config(self) -> RiskConfig:
136-
"""Get configuration for RiskManager."""
175+
"""
176+
Get configuration for RiskManager.
177+
178+
Returns:
179+
RiskConfig: The configuration for the RiskManager.
180+
"""
181+
if self.risk_config:
182+
return self.risk_config
137183
return RiskConfig(
138184
max_risk_per_trade=0.01, # 1% per trade
139185
max_daily_loss=0.03, # 3% daily loss
@@ -208,30 +254,25 @@ def __init__(
208254
self.journal = None # TODO: Future enhancement
209255
self.analytics = None # TODO: Future enhancement
210256

211-
# Initialize risk manager first if enabled (needed by PositionManager)
212-
if Features.RISK_MANAGER in config.features:
213-
# Create RiskManager without position_manager first
214-
self.risk_manager = RiskManager(
215-
project_x=client,
216-
order_manager=self.orders,
217-
event_bus=self.events,
218-
position_manager=None, # Will be set after PositionManager is created
219-
config=config.get_risk_config(),
220-
)
221-
222-
# Now create PositionManager with risk_manager
257+
# Create PositionManager first
223258
self.positions = PositionManager(
224259
client,
225260
event_bus=self.events,
226-
risk_manager=self.risk_manager,
227-
data_manager=self.data, # type: ignore
261+
risk_manager=None, # Will be set later
262+
data_manager=self.data,
228263
config=config.get_position_manager_config(),
229264
)
230265

231-
# Update RiskManager with position_manager reference if it exists
232-
if self.risk_manager:
233-
self.risk_manager.position_manager = self.positions
234-
self.risk_manager.positions = self.positions # Also set positions attribute
266+
# Initialize risk manager if enabled and inject dependencies
267+
if Features.RISK_MANAGER in config.features:
268+
self.risk_manager = RiskManager(
269+
project_x=cast(ProjectXClientProtocol, client),
270+
order_manager=self.orders,
271+
event_bus=self.events,
272+
position_manager=self.positions,
273+
config=config.get_risk_config(),
274+
)
275+
self.positions.risk_manager = self.risk_manager
235276

236277
# State tracking
237278
self._connected = False
@@ -791,53 +832,64 @@ def get_stats(self) -> TradingSuiteStats:
791832
# Build component stats
792833
components: dict[str, ComponentStats] = {}
793834
if self.orders:
835+
last_activity_obj = self.orders.stats.get("last_order_time")
794836
components["order_manager"] = ComponentStats(
795837
name="OrderManager",
796838
status="connected" if self.orders else "disconnected",
797839
uptime_seconds=uptime_seconds,
798-
last_activity=None,
799-
error_count=0,
800-
memory_usage_mb=0.0,
840+
last_activity=last_activity_obj.isoformat()
841+
if last_activity_obj
842+
else None,
843+
error_count=0, # TODO: Implement error tracking in OrderManager
844+
memory_usage_mb=0.0, # TODO: Implement memory tracking in OrderManager
801845
)
802846

803847
if self.positions:
848+
last_activity_obj = self.positions.stats.get("last_position_update")
804849
components["position_manager"] = ComponentStats(
805850
name="PositionManager",
806851
status="connected" if self.positions else "disconnected",
807852
uptime_seconds=uptime_seconds,
808-
last_activity=None,
809-
error_count=0,
810-
memory_usage_mb=0.0,
853+
last_activity=last_activity_obj.isoformat()
854+
if last_activity_obj
855+
else None,
856+
error_count=0, # TODO: Implement error tracking in PositionManager
857+
memory_usage_mb=0.0, # TODO: Implement memory tracking in PositionManager
811858
)
812859

813860
if self.data:
861+
last_activity_obj = self.data.memory_stats.get("last_update")
814862
components["data_manager"] = ComponentStats(
815863
name="RealtimeDataManager",
816864
status="connected" if self.data else "disconnected",
817865
uptime_seconds=uptime_seconds,
818-
last_activity=None,
819-
error_count=0,
820-
memory_usage_mb=0.0,
866+
last_activity=last_activity_obj.isoformat()
867+
if last_activity_obj
868+
else None,
869+
error_count=self.data.memory_stats.get("data_validation_errors", 0),
870+
memory_usage_mb=self.data.memory_stats.get("memory_usage_mb", 0.0),
821871
)
822872

823873
if self.orderbook:
824874
components["orderbook"] = ComponentStats(
825875
name="OrderBook",
826876
status="connected" if self.orderbook else "disconnected",
827877
uptime_seconds=uptime_seconds,
828-
last_activity=None,
829-
error_count=0,
830-
memory_usage_mb=0.0,
878+
last_activity=self.orderbook.last_orderbook_update.isoformat()
879+
if self.orderbook.last_orderbook_update
880+
else None,
881+
error_count=0, # TODO: Implement error tracking in OrderBook
882+
memory_usage_mb=0.0, # TODO: Implement memory tracking in OrderBook
831883
)
832884

833885
if self.risk_manager:
834886
components["risk_manager"] = ComponentStats(
835887
name="RiskManager",
836888
status="active" if self.risk_manager else "inactive",
837889
uptime_seconds=uptime_seconds,
838-
last_activity=None,
839-
error_count=0,
840-
memory_usage_mb=0.0,
890+
last_activity=None, # TODO: Implement activity tracking in RiskManager
891+
error_count=0, # TODO: Implement error tracking in RiskManager
892+
memory_usage_mb=0.0, # TODO: Implement memory tracking in RiskManager
841893
)
842894

843895
return {

0 commit comments

Comments
 (0)