Skip to content

Commit 4f4f295

Browse files
Fixes to Market Data connections
1 parent 7e033d8 commit 4f4f295

8 files changed

Lines changed: 1212 additions & 213 deletions

File tree

examples/README.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,189 @@ Some examples require additional Python packages:
4444
- All examples connect to the demo environment by default. To use the live environment, set the `PROJECTX_ENVIRONMENT` environment variable to "live".
4545
- The examples are designed to be interactive and will prompt for user input when needed.
4646
- Make sure your API credentials have the necessary permissions for the actions demonstrated in each example.
47+
48+
## Running the Examples
49+
50+
To run these examples, you'll need:
51+
52+
1. A ProjectX account with API access
53+
2. Your username and API key
54+
55+
Set these as environment variables:
56+
57+
```bash
58+
export PROJECTX_USERNAME=your_username
59+
export PROJECTX_API_KEY=your_api_key
60+
```
61+
62+
Then run an example:
63+
64+
```bash
65+
python realtime_example.py
66+
```
67+
68+
## Troubleshooting Realtime Connections
69+
70+
If you're experiencing issues with realtime connections, follow this troubleshooting guide:
71+
72+
### Common Issues and Solutions
73+
74+
#### No events being received
75+
76+
If you've subscribed to market events but aren't receiving any callbacks:
77+
78+
1. **Check Authentication**: Ensure your API key is valid and has appropriate permissions.
79+
```python
80+
# Verify authentication is working
81+
accounts = client.accounts.get_accounts()
82+
print(accounts) # If this fails, you have an authentication issue
83+
```
84+
85+
2. **Verify Connection Establishment**: Add debug logging to check connection status.
86+
```python
87+
import logging
88+
logging.basicConfig(level=logging.DEBUG)
89+
```
90+
91+
3. **Check Method Names**: The exact method names must match between client and server.
92+
- SubscribeContractQuotes
93+
- SubscribeContractTrades
94+
- SubscribeContractMarketDepth
95+
96+
4. **Check Contract IDs**: Verify the contract ID is valid and properly formatted.
97+
```python
98+
# Get all available contracts
99+
contracts = client.contracts.search_contracts()
100+
valid_ids = [c["id"] for c in contracts["contracts"]]
101+
print(valid_ids)
102+
```
103+
104+
5. **Enable Signal-R Debug Mode**: Directly configure the SignalR connection:
105+
```python
106+
# Add more debug logging
107+
import signalrcore.hub.errors
108+
signalrcore.hub.errors.HubError.verbose = True
109+
```
110+
111+
6. **Verify Event Names**: The events from the server must match these exactly:
112+
- GatewayQuote
113+
- GatewayTrade
114+
- GatewayDepth
115+
116+
7. **Check Callback Function Signatures**: Your callbacks should accept 2 parameters:
117+
```python
118+
def handle_quote(contract_id: str, data: dict):
119+
# Both parameters are required
120+
print(f"Contract: {contract_id}, Data: {data}")
121+
```
122+
123+
8. **Connection Timing**: Ensure the connection is fully established before subscribing:
124+
```python
125+
# Start first, then subscribe
126+
realtime.start()
127+
time.sleep(2) # Give it time to connect
128+
market_hub.subscribe_quotes(contract_id, handle_quote)
129+
```
130+
131+
#### Connection Drops or Errors
132+
133+
If the connection is unstable or drops frequently:
134+
135+
1. **Check Network Stability**: Realtime connections require stable internet.
136+
137+
2. **Implement Reconnect Logic**: Use the built-in reconnect functionality:
138+
```python
139+
# The SDK handles reconnection automatically
140+
# You can also manually reconnect
141+
realtime.stop()
142+
time.sleep(1)
143+
realtime.start() # This will reestablish connections and resubscribe
144+
```
145+
146+
3. **Check for Credential Expiration**: Ensure your token hasn't expired.
147+
The SDK should handle this automatically, but you can verify:
148+
```python
149+
# Force token refresh
150+
client.auth.validate_token()
151+
```
152+
153+
### Debugging Tools
154+
155+
1. **Connection Testing Script**:
156+
```python
157+
import time
158+
from projectx_sdk import ProjectXClient
159+
from projectx_sdk.realtime import RealtimeService
160+
161+
# Enable verbose logging
162+
import logging
163+
logging.basicConfig(level=logging.DEBUG)
164+
165+
client = ProjectXClient(username="...", api_key="...", environment="demo")
166+
realtime = RealtimeService(client)
167+
168+
# Connection state callbacks
169+
def on_connect():
170+
print("CONNECTED!")
171+
172+
def on_disconnect():
173+
print("DISCONNECTED!")
174+
175+
# Manually attach to connection events
176+
realtime.market._connection.on_open(on_connect)
177+
realtime.market._connection.on_close(on_disconnect)
178+
179+
# Start the connection
180+
realtime.start()
181+
182+
# Keep alive
183+
try:
184+
while True:
185+
time.sleep(1)
186+
except KeyboardInterrupt:
187+
realtime.stop()
188+
```
189+
190+
2. **Data Structure Inspection**:
191+
```python
192+
def debug_callback(contract_id, data):
193+
import json
194+
print(f"CONTRACT: {contract_id}")
195+
print(f"DATA: {json.dumps(data, indent=2)}")
196+
197+
market_hub.subscribe_quotes("CON.F.US.ENQ.H25", debug_callback)
198+
```
199+
200+
### Advanced Troubleshooting
201+
202+
If basic troubleshooting doesn't resolve your issue:
203+
204+
1. **Direct SignalR Connection**: Bypass the SDK abstraction layer:
205+
```python
206+
from signalrcore.hub_connection_builder import HubConnectionBuilder
207+
208+
token = client.auth.get_token()
209+
hub_url = "https://gateway-api-demo.s2f.projectx.com/hubs/market"
210+
211+
connection = (
212+
HubConnectionBuilder()
213+
.with_url(f"{hub_url}?access_token={token}")
214+
.with_automatic_reconnect({
215+
"type": "raw",
216+
"keep_alive_interval": 10,
217+
"reconnect_interval": 5,
218+
"max_attempts": 10,
219+
})
220+
.build()
221+
)
222+
223+
connection.on("GatewayQuote", lambda contract_id, data:
224+
print(f"QUOTE: {contract_id} - {data}"))
225+
226+
connection.start()
227+
connection.invoke("SubscribeContractQuotes", "CON.F.US.ENQ.H25")
228+
```
229+
230+
2. **Network Traffic Analysis**: Use tools like Wireshark to inspect the WebSocket traffic.
231+
232+
3. **Check Server Status**: Verify the ProjectX API status and whether there are any known issues.

examples/realtime_example.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
"""
2+
Example of using ProjectX SDK realtime features.
3+
4+
This example demonstrates how to properly set up and use the MarketHub
5+
to subscribe to quote, trade, and market depth events.
6+
"""
7+
8+
import logging
9+
import os
10+
import signal
11+
import sys
12+
import time
13+
from typing import Any, Dict
14+
15+
from projectx_sdk import ProjectXClient
16+
from projectx_sdk.realtime import RealtimeService
17+
18+
# Set up logging
19+
logging.basicConfig(
20+
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
21+
)
22+
logger = logging.getLogger("projectx_example")
23+
24+
# Global flag to control execution
25+
running = True
26+
27+
28+
def setup_signal_handlers():
29+
"""Set up signal handlers to gracefully handle termination."""
30+
31+
def signal_handler(sig, frame):
32+
global running
33+
logger.info("Received shutdown signal, closing connections...")
34+
running = False
35+
36+
signal.signal(signal.SIGINT, signal_handler)
37+
signal.signal(signal.SIGTERM, signal_handler)
38+
39+
40+
def handle_quote(contract_id: str, data: Dict[str, Any]):
41+
"""
42+
Handle quote updates.
43+
44+
Args:
45+
contract_id: The contract ID
46+
data: Quote data dictionary
47+
"""
48+
bid = data.get("bid")
49+
ask = data.get("ask")
50+
last = data.get("last")
51+
timestamp = data.get("timestamp", "N/A")
52+
53+
logger.info(f"Quote [{contract_id}]: Bid={bid}, Ask={ask}, Last={last} @ {timestamp}")
54+
55+
56+
def handle_trade(contract_id: str, data: Dict[str, Any]):
57+
"""
58+
Handle trade updates.
59+
60+
Args:
61+
contract_id: The contract ID
62+
data: Trade data dictionary
63+
"""
64+
price = data.get("price")
65+
size = data.get("size")
66+
timestamp = data.get("timestamp", "N/A")
67+
68+
logger.info(f"Trade [{contract_id}]: Price={price}, Size={size} @ {timestamp}")
69+
70+
71+
def handle_depth(contract_id: str, data: Dict[str, Any]):
72+
"""
73+
Handle market depth updates.
74+
75+
Args:
76+
contract_id: The contract ID
77+
data: Market depth data dictionary
78+
"""
79+
bids = data.get("bids", [])
80+
asks = data.get("asks", [])
81+
82+
top_bid = bids[0] if bids else {"price": "N/A", "size": "N/A"}
83+
top_ask = asks[0] if asks else {"price": "N/A", "size": "N/A"}
84+
85+
logger.info(
86+
f"Depth [{contract_id}]: Top Bid={top_bid['price']} x {top_bid['size']}, "
87+
f"Top Ask={top_ask['price']} x {top_ask['size']}"
88+
)
89+
90+
91+
def main():
92+
"""Run the example."""
93+
# Set up signal handlers for graceful shutdown
94+
setup_signal_handlers()
95+
96+
# Get credentials from environment variables
97+
username = os.environ.get("PROJECTX_USERNAME")
98+
api_key = os.environ.get("PROJECTX_API_KEY")
99+
environment = os.environ.get("PROJECTX_ENVIRONMENT")
100+
101+
if not username or not api_key:
102+
logger.error("Please set PROJECTX_USERNAME and PROJECTX_API_KEY environment variables")
103+
return 1
104+
105+
# Contract to subscribe to
106+
contract_id = "CON.F.US.ENQ.H25" # E-mini NASDAQ-100 March 2025
107+
108+
try:
109+
# Create and authenticate client
110+
logger.info("Initializing client...")
111+
client = ProjectXClient(username=username, api_key=api_key, environment=environment)
112+
113+
# Create realtime service
114+
logger.info("Setting up realtime connections...")
115+
realtime = RealtimeService(client)
116+
117+
# Access the market hub (lazy initialization)
118+
market_hub = realtime.market
119+
120+
# Subscribe to market data events
121+
logger.info(f"Subscribing to market data for {contract_id}...")
122+
market_hub.subscribe_quotes(contract_id, handle_quote)
123+
market_hub.subscribe_trades(contract_id, handle_trade)
124+
market_hub.subscribe_market_depth(contract_id, handle_depth)
125+
126+
# Start the connection
127+
logger.info("Starting realtime connection...")
128+
realtime.start()
129+
130+
# Main loop - keep the program running
131+
logger.info("Listening for market events. Press Ctrl+C to exit.")
132+
while running:
133+
time.sleep(1)
134+
135+
except KeyboardInterrupt:
136+
logger.info("Keyboard interrupt received, shutting down...")
137+
except Exception as e:
138+
logger.exception(f"Error in realtime example: {e}")
139+
return 1
140+
finally:
141+
# Clean up
142+
if "realtime" in locals():
143+
logger.info("Stopping realtime connections...")
144+
try:
145+
realtime.stop()
146+
except Exception as e:
147+
logger.error(f"Error stopping realtime service: {e}")
148+
149+
logger.info("Example completed successfully")
150+
return 0
151+
152+
153+
if __name__ == "__main__":
154+
sys.exit(main())

0 commit comments

Comments
 (0)