Skip to content

Commit efd3461

Browse files
Environment URL updates
1 parent 33dc896 commit efd3461

4 files changed

Lines changed: 138 additions & 43 deletions

File tree

projectx_sdk/client.py

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,61 @@ class ProjectXClient:
3535

3636
# Map of environment names to base URLs
3737
ENVIRONMENT_URLS = {
38-
"alphaticks": "https://gateway-api-alphaticks.s2f.projectx.com",
39-
"blueguardian": "https://gateway-api-blueguardian.s2f.projectx.com",
40-
"blusky": "https://gateway-api-blusky.s2f.projectx.com",
41-
"e8x": "https://gateway-api-e8x.s2f.projectx.com",
42-
"fundingfutures": "https://gateway-api-fundingfutures.s2f.projectx.com",
43-
"thefuturesdesk": "https://gateway-api-thefuturesdesk.s2f.projectx.com",
44-
"futureselite": "https://gateway-api-futureselite.s2f.projectx.com",
45-
"fxifyfutures": "https://gateway-api-fxifyfutures.s2f.projectx.com",
46-
"goatfunded": "https://gateway-api-goatfunded.s2f.projectx.com",
47-
"tickticktrader": "https://gateway-api-tickticktrader.s2f.projectx.com",
48-
"toponefutures": "https://gateway-api-toponefutures.s2f.projectx.com",
49-
"topstepx": "https://gateway-api-topstepx.s2f.projectx.com",
50-
"tx3funding": "https://gateway-api-tx3funding.s2f.projectx.com",
38+
"alphaticks": "https://api.alphaticks.projectx.com",
39+
"blueguardian": "https://api.blueguardianfutures.projectx.com",
40+
"blusky": "https://api.blusky.projectx.com",
41+
"e8x": "https://api.e8.projectx.com",
42+
"fundingfutures": "https://api.fundingfutures.projectx.com",
43+
"thefuturesdesk": "https://api.thefuturesdesk.projectx.com",
44+
"futureselite": "https://api.futureselite.projectx.com",
45+
"fxifyfutures": "https://api.fxifyfutures.projectx.com",
46+
"goatfunded": "https://api.goatfundedfutures.projectx.com",
47+
"tickticktrader": "https://api.tickticktrader.projectx.com",
48+
"toponefutures": "https://api.toponefutures.projectx.com",
49+
"topstepx": "https://api.topstepx.com",
50+
"tx3funding": "https://api.tx3funding.projectx.com",
5151
# For testing/demo
5252
"demo": "https://gateway-api-demo.s2f.projectx.com",
5353
}
5454

55+
# Map of environment names to user hub URLs
56+
USER_HUB_URLS = {
57+
"alphaticks": "https://rtc.alphaticks.projectx.com/hubs/user",
58+
"blueguardian": "https://rtc.blueguardianfutures.projectx.com/hubs/user",
59+
"blusky": "https://rtc.blusky.projectx.com/hubs/user",
60+
"e8x": "https://rtc.e8.projectx.com/hubs/user",
61+
"fundingfutures": "https://rtc.fundingfutures.projectx.com/hubs/user",
62+
"thefuturesdesk": "https://rtc.thefuturesdesk.projectx.com/hubs/user",
63+
"futureselite": "https://rtc.futureselite.projectx.com/hubs/user",
64+
"fxifyfutures": "https://rtc.fxifyfutures.projectx.com/hubs/user",
65+
"goatfunded": "https://rtc.goatfundedfutures.projectx.com/hubs/user",
66+
"tickticktrader": "https://rtc.tickticktrader.projectx.com/hubs/user",
67+
"toponefutures": "https://rtc.toponefutures.projectx.com/hubs/user",
68+
"topstepx": "https://rtc.topstepx.com/hubs/user",
69+
"tx3funding": "https://rtc.tx3funding.projectx.com/hubs/user",
70+
# For testing/demo
71+
"demo": "https://gateway-api-demo.s2f.projectx.com/hubs/user",
72+
}
73+
74+
# Map of environment names to market hub URLs
75+
MARKET_HUB_URLS = {
76+
"alphaticks": "https://rtc.alphaticks.projectx.com/hubs/market",
77+
"blueguardian": "https://rtc.blueguardianfutures.projectx.com/hubs/market",
78+
"blusky": "https://rtc.blusky.projectx.com/hubs/market",
79+
"e8x": "https://rtc.e8.projectx.com/hubs/market",
80+
"fundingfutures": "https://rtc.fundingfutures.projectx.com/hubs/market",
81+
"thefuturesdesk": "https://rtc.thefuturesdesk.projectx.com/hubs/market",
82+
"futureselite": "https://rtc.futureselite.projectx.com/hubs/market",
83+
"fxifyfutures": "https://rtc.fxifyfutures.projectx.com/hubs/market",
84+
"goatfunded": "https://rtc.goatfundedfutures.projectx.com/hubs/market",
85+
"tickticktrader": "https://rtc.tickticktrader.projectx.com/hubs/market",
86+
"toponefutures": "https://rtc.toponefutures.projectx.com/hubs/market",
87+
"topstepx": "https://rtc.topstepx.com/hubs/market",
88+
"tx3funding": "https://rtc.tx3funding.projectx.com/hubs/market",
89+
# For testing/demo
90+
"demo": "https://gateway-api-demo.s2f.projectx.com/hubs/market",
91+
}
92+
5593
def __init__(
5694
self,
5795
username: Optional[str] = None,
@@ -132,7 +170,12 @@ def realtime(self) -> RealTimeClient:
132170
"""
133171
if not self._realtime:
134172
token = self.auth.get_token()
135-
self._realtime = RealTimeClient(auth_token=token, environment=self.environment)
173+
self._realtime = RealTimeClient(
174+
auth_token=token,
175+
environment=self.environment,
176+
user_hub_url=self.USER_HUB_URLS.get(self.environment),
177+
market_hub_url=self.MARKET_HUB_URLS.get(self.environment),
178+
)
136179
return self._realtime
137180

138181
def request(

projectx_sdk/realtime/__init__.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Real-time communication modules for ProjectX Gateway API."""
22

3+
from typing import Optional
4+
35
from projectx_sdk.realtime.connection import SignalRConnection
46
from projectx_sdk.realtime.market_hub import MarketHub
57
from projectx_sdk.realtime.user_hub import UserHub
@@ -12,26 +14,37 @@ class RealTimeClient:
1214
Manages connections to the User and Market hubs for real-time data.
1315
"""
1416

15-
def __init__(self, auth_token: str, environment: str):
17+
def __init__(
18+
self,
19+
auth_token: str,
20+
environment: str,
21+
user_hub_url: Optional[str] = None,
22+
market_hub_url: Optional[str] = None,
23+
):
1624
"""
1725
Initialize a real-time client.
1826
1927
Args:
2028
auth_token: JWT auth token for API access
2129
environment: Environment name (e.g., 'topstepx')
30+
user_hub_url: URL for the user hub (optional)
31+
market_hub_url: URL for the market hub (optional)
2232
"""
2333
# Create hub instances first
2434
self.user = UserHub.__new__(UserHub)
2535
self.market = MarketHub.__new__(MarketHub)
2636

37+
# Use provided URLs or generate default ones based on environment
38+
default_base_url = f"wss://gateway-rtc-{environment}.s2f.projectx.com"
39+
2740
# Initialize connections with the hub callbacks
2841
self._user_connection = SignalRConnection(
29-
hub_url=f"wss://gateway-rtc-{environment}.s2f.projectx.com/hubs/user",
42+
hub_url=user_hub_url or f"{default_base_url}/hubs/user",
3043
access_token=auth_token,
3144
connection_callback=self.user._on_connected,
3245
)
3346
self._market_connection = SignalRConnection(
34-
hub_url=f"wss://gateway-rtc-{environment}.s2f.projectx.com/hubs/market",
47+
hub_url=market_hub_url or f"{default_base_url}/hubs/market",
3548
access_token=auth_token,
3649
connection_callback=self.market._on_connected,
3750
)
@@ -100,8 +113,19 @@ def __init__(self, client):
100113
self._client = client
101114
self._user = None
102115
self._market = None
116+
117+
# For backward compatibility with tests, always set base_hub_url
103118
self._base_hub_url = f"wss://gateway-rtc-{client.environment}.s2f.projectx.com"
104119

120+
# Check if we should use the new URL pattern from client.USER_HUB_URLS
121+
if hasattr(client, "USER_HUB_URLS") and client.environment in client.USER_HUB_URLS:
122+
self._user_hub_url = client.USER_HUB_URLS.get(client.environment)
123+
self._market_hub_url = client.MARKET_HUB_URLS.get(client.environment)
124+
else:
125+
# Fallback to old URL pattern
126+
self._user_hub_url = None
127+
self._market_hub_url = None
128+
105129
@property
106130
def user(self):
107131
"""
@@ -111,7 +135,10 @@ def user(self):
111135
UserHub: The user hub instance
112136
"""
113137
if self._user is None:
114-
self._user = UserHub(self._client, self._base_hub_url)
138+
if self._user_hub_url:
139+
self._user = UserHub(self._client, self._base_hub_url, self._user_hub_url)
140+
else:
141+
self._user = UserHub(self._client, self._base_hub_url)
115142
return self._user
116143

117144
@property
@@ -125,7 +152,10 @@ def market(self):
125152
if self._market is None:
126153
from projectx_sdk.realtime.market_hub import MarketHub
127154

128-
self._market = MarketHub(self._client, self._base_hub_url)
155+
if self._market_hub_url:
156+
self._market = MarketHub(self._client, self._base_hub_url, self._market_hub_url)
157+
else:
158+
self._market = MarketHub(self._client, self._base_hub_url)
129159
return self._market
130160

131161
def start(self):

projectx_sdk/realtime/market_hub.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@ class MarketHub:
1616
such as quotes, trades, and market depth (order book).
1717
"""
1818

19-
def __init__(self, client_or_connection, base_hub_url=None):
19+
def __init__(self, client_or_connection, base_hub_url=None, hub_url=None):
2020
"""
2121
Initialize the market hub.
2222
23-
This constructor supports two signatures for backward compatibility:
23+
This constructor supports multiple signatures for flexibility:
2424
1. MarketHub(client, base_hub_url) - legacy construction using client and base URL
25-
2. MarketHub(connection) - new construction using a SignalRConnection directly
25+
2. MarketHub(client, None, hub_url) - construction using client and direct hub URL
26+
3. MarketHub(connection) - construction using a SignalRConnection directly
2627
2728
Args:
2829
client_or_connection: Either a ProjectXClient instance or a SignalRConnection
29-
base_hub_url (str, optional): The base hub URL (required for legacy constructor)
30+
base_hub_url (str, optional): The base hub URL (for legacy constructor)
31+
hub_url (str, optional): The complete hub URL (overrides base_hub_url)
3032
"""
3133
# Initialize instance variables first
3234
self.__init_instance_vars()
@@ -38,19 +40,28 @@ def __init__(self, client_or_connection, base_hub_url=None):
3840
self._is_connected = self._connection.is_connected()
3941
self._owns_connection = False
4042
else:
41-
# Legacy constructor with client and base_hub_url
42-
if base_hub_url is None:
43-
raise ValueError("base_hub_url is required when using client-based constructor")
44-
43+
# Constructor with client and URL
4544
self._client = client_or_connection
46-
self.base_hub_url = base_hub_url
47-
self.hub_path = "/hubs/market"
48-
self.hub_url = f"{base_hub_url}{self.hub_path}"
45+
self._owns_connection = True
46+
47+
if hub_url:
48+
# Direct hub URL provided
49+
self.hub_url = hub_url
50+
self.base_hub_url = None
51+
self.hub_path = None
52+
elif base_hub_url:
53+
# Base URL provided, construct hub URL
54+
self.base_hub_url = base_hub_url
55+
self.hub_path = "/hubs/market"
56+
self.hub_url = f"{base_hub_url}{self.hub_path}"
57+
else:
58+
raise ValueError(
59+
"Either base_hub_url or hub_url is required when using client-based constructor"
60+
)
4961

5062
# Initialize connection but don't start yet
5163
self._connection: Optional[SignalRConnection] = None # type: ignore
5264
self._is_connected = False
53-
self._owns_connection = True
5465

5566
# Register event handlers if using direct connection
5667
if not self._owns_connection:

projectx_sdk/realtime/user_hub.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,19 @@ class UserHub:
1919
- Trade (execution) notifications
2020
"""
2121

22-
def __init__(self, client_or_connection, base_hub_url=None):
22+
def __init__(self, client_or_connection, base_hub_url=None, hub_url=None):
2323
"""
2424
Initialize the user hub connection.
2525
26-
This constructor supports two signatures for backward compatibility:
26+
This constructor supports multiple signatures for flexibility:
2727
1. UserHub(client, base_hub_url) - legacy construction using client and base URL
28-
2. UserHub(connection) - new construction using a SignalRConnection directly
28+
2. UserHub(client, None, hub_url) - construction using client and direct hub URL
29+
3. UserHub(connection) - construction using a SignalRConnection directly
2930
3031
Args:
3132
client_or_connection: Either a ProjectXClient instance or a SignalRConnection
32-
base_hub_url (str, optional): The base hub URL (required for legacy constructor)
33+
base_hub_url (str, optional): The base hub URL (for legacy constructor)
34+
hub_url (str, optional): The complete hub URL (overrides base_hub_url)
3335
"""
3436
# Initialize instance variables first
3537
self.__init_instance_vars()
@@ -41,19 +43,28 @@ def __init__(self, client_or_connection, base_hub_url=None):
4143
self._is_connected = self._connection.is_connected()
4244
self._owns_connection = False
4345
else:
44-
# Legacy constructor with client and base_hub_url
45-
if base_hub_url is None:
46-
raise ValueError("base_hub_url is required when using client-based constructor")
47-
46+
# Constructor with client and URL
4847
self._client = client_or_connection
49-
self.base_hub_url = base_hub_url
50-
self.hub_path = "/hubs/user"
51-
self.hub_url = f"{base_hub_url}{self.hub_path}"
48+
self._owns_connection = True
49+
50+
if hub_url:
51+
# Direct hub URL provided
52+
self.hub_url = hub_url
53+
self.base_hub_url = None
54+
self.hub_path = None
55+
elif base_hub_url:
56+
# Base URL provided, construct hub URL
57+
self.base_hub_url = base_hub_url
58+
self.hub_path = "/hubs/user"
59+
self.hub_url = f"{base_hub_url}{self.hub_path}"
60+
else:
61+
raise ValueError(
62+
"Either base_hub_url or hub_url is required when using client-based constructor"
63+
)
5264

5365
# Initialize connection but don't start yet
5466
self._connection: Optional[SignalRConnection] = None # type: ignore
5567
self._is_connected = False
56-
self._owns_connection = True
5768

5869
# Register handlers if using direct connection
5970
if not self._owns_connection:

0 commit comments

Comments
 (0)