quantjourney-bidask 0.9.4__py3-none-any.whl → 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantjourney_bidask/__init__.py +31 -4
- quantjourney_bidask/_compare_edge.py +152 -0
- quantjourney_bidask/edge.py +149 -127
- quantjourney_bidask/edge_expanding.py +45 -57
- quantjourney_bidask/edge_hft.py +126 -0
- quantjourney_bidask/edge_rolling.py +54 -198
- {quantjourney_bidask-0.9.4.dist-info → quantjourney_bidask-1.0.1.dist-info}/METADATA +94 -35
- quantjourney_bidask-1.0.1.dist-info/RECORD +11 -0
- quantjourney_bidask/_version.py +0 -7
- quantjourney_bidask/websocket_fetcher.py +0 -308
- quantjourney_bidask-0.9.4.dist-info/RECORD +0 -11
- {quantjourney_bidask-0.9.4.dist-info → quantjourney_bidask-1.0.1.dist-info}/WHEEL +0 -0
- {quantjourney_bidask-0.9.4.dist-info → quantjourney_bidask-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {quantjourney_bidask-0.9.4.dist-info → quantjourney_bidask-1.0.1.dist-info}/top_level.txt +0 -0
@@ -1,308 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
WebSocket Live Data Fetcher
|
3
|
-
|
4
|
-
Real-time data fetching for cryptocurrency exchanges using WebSockets.
|
5
|
-
"""
|
6
|
-
|
7
|
-
import json
|
8
|
-
import threading
|
9
|
-
import time
|
10
|
-
import pandas as pd
|
11
|
-
import numpy as np
|
12
|
-
from datetime import datetime, timezone
|
13
|
-
from typing import Dict, List, Callable, Optional
|
14
|
-
import websocket
|
15
|
-
from collections import deque
|
16
|
-
from .edge_rolling import edge_rolling
|
17
|
-
|
18
|
-
class LiveSpreadMonitor:
|
19
|
-
"""
|
20
|
-
Real-time spread monitoring using WebSocket connections.
|
21
|
-
|
22
|
-
Supports Binance WebSocket streams for live OHLC data and real-time
|
23
|
-
spread calculation with configurable alerts.
|
24
|
-
"""
|
25
|
-
|
26
|
-
def __init__(self, symbols: List[str], window: int = 20, buffer_size: int = 1000):
|
27
|
-
"""
|
28
|
-
Initialize the live spread monitor.
|
29
|
-
|
30
|
-
Parameters
|
31
|
-
----------
|
32
|
-
symbols : List[str]
|
33
|
-
List of trading symbols to monitor (e.g., ['BTCUSDT', 'ETHUSDT'])
|
34
|
-
window : int
|
35
|
-
Rolling window size for spread calculation
|
36
|
-
buffer_size : int
|
37
|
-
Maximum number of candles to keep in memory
|
38
|
-
"""
|
39
|
-
self.symbols = [s.lower() for s in symbols]
|
40
|
-
self.window = window
|
41
|
-
self.buffer_size = buffer_size
|
42
|
-
|
43
|
-
# Data storage
|
44
|
-
self.data_buffers = {symbol: deque(maxlen=buffer_size) for symbol in self.symbols}
|
45
|
-
self.spread_buffers = {symbol: deque(maxlen=buffer_size) for symbol in self.symbols}
|
46
|
-
|
47
|
-
# WebSocket connections
|
48
|
-
self.ws_connections = {}
|
49
|
-
self.running = False
|
50
|
-
|
51
|
-
# Callbacks
|
52
|
-
self.data_callbacks = []
|
53
|
-
self.alert_callbacks = []
|
54
|
-
|
55
|
-
# Alert thresholds (in basis points)
|
56
|
-
self.alert_thresholds = {symbol: {'high': 100, 'low': 5} for symbol in self.symbols}
|
57
|
-
|
58
|
-
def add_data_callback(self, callback: Callable):
|
59
|
-
"""Add callback function for new data events."""
|
60
|
-
self.data_callbacks.append(callback)
|
61
|
-
|
62
|
-
def add_alert_callback(self, callback: Callable):
|
63
|
-
"""Add callback function for alert events."""
|
64
|
-
self.alert_callbacks.append(callback)
|
65
|
-
|
66
|
-
def set_alert_threshold(self, symbol: str, high_bps: float, low_bps: float):
|
67
|
-
"""Set alert thresholds for a symbol (in basis points)."""
|
68
|
-
symbol = symbol.lower()
|
69
|
-
if symbol in self.alert_thresholds:
|
70
|
-
self.alert_thresholds[symbol] = {'high': high_bps, 'low': low_bps}
|
71
|
-
|
72
|
-
def _create_websocket_url(self, symbols: List[str]) -> str:
|
73
|
-
"""Create Binance WebSocket URL for multiple symbols."""
|
74
|
-
streams = []
|
75
|
-
for symbol in symbols:
|
76
|
-
streams.append(f"{symbol}@kline_1m") # 1-minute klines
|
77
|
-
|
78
|
-
if len(streams) == 1:
|
79
|
-
return f"wss://stream.binance.com:9443/ws/{streams[0]}"
|
80
|
-
else:
|
81
|
-
stream_string = "/".join(streams)
|
82
|
-
return f"wss://stream.binance.com:9443/stream?streams={stream_string}"
|
83
|
-
|
84
|
-
def _on_message(self, ws, message):
|
85
|
-
"""Handle incoming WebSocket messages."""
|
86
|
-
try:
|
87
|
-
data = json.loads(message)
|
88
|
-
|
89
|
-
# Handle multi-stream format
|
90
|
-
if 'stream' in data:
|
91
|
-
stream_data = data['data']
|
92
|
-
symbol = stream_data['s'].lower()
|
93
|
-
else:
|
94
|
-
stream_data = data
|
95
|
-
symbol = stream_data['s'].lower()
|
96
|
-
|
97
|
-
# Extract kline data
|
98
|
-
kline = stream_data['k']
|
99
|
-
is_closed = kline['x'] # Whether kline is closed
|
100
|
-
|
101
|
-
if is_closed: # Only process closed candles
|
102
|
-
candle_data = {
|
103
|
-
'timestamp': pd.to_datetime(kline['t'], unit='ms'),
|
104
|
-
'symbol': symbol,
|
105
|
-
'open': float(kline['o']),
|
106
|
-
'high': float(kline['h']),
|
107
|
-
'low': float(kline['l']),
|
108
|
-
'close': float(kline['c']),
|
109
|
-
'volume': float(kline['v'])
|
110
|
-
}
|
111
|
-
|
112
|
-
self._process_candle(candle_data)
|
113
|
-
|
114
|
-
except Exception as e:
|
115
|
-
print(f"Error processing message: {e}")
|
116
|
-
|
117
|
-
def _process_candle(self, candle_data: Dict):
|
118
|
-
"""Process new candle data and update spreads."""
|
119
|
-
symbol = candle_data['symbol']
|
120
|
-
|
121
|
-
# Add to buffer
|
122
|
-
self.data_buffers[symbol].append(candle_data)
|
123
|
-
|
124
|
-
# Calculate spread if we have enough data
|
125
|
-
if len(self.data_buffers[symbol]) >= self.window:
|
126
|
-
# Convert buffer to DataFrame for spread calculation
|
127
|
-
df = pd.DataFrame(list(self.data_buffers[symbol])[-self.window:])
|
128
|
-
|
129
|
-
# Calculate current spread
|
130
|
-
try:
|
131
|
-
current_spread = edge_rolling(df.tail(1), window=min(len(df), self.window)).iloc[-1]
|
132
|
-
|
133
|
-
if not pd.isna(current_spread):
|
134
|
-
spread_bps = current_spread * 10000 # Convert to basis points
|
135
|
-
|
136
|
-
spread_data = {
|
137
|
-
'timestamp': candle_data['timestamp'],
|
138
|
-
'symbol': symbol,
|
139
|
-
'spread_bps': spread_bps,
|
140
|
-
'price': candle_data['close']
|
141
|
-
}
|
142
|
-
|
143
|
-
self.spread_buffers[symbol].append(spread_data)
|
144
|
-
|
145
|
-
# Check for alerts
|
146
|
-
self._check_alerts(spread_data)
|
147
|
-
|
148
|
-
# Notify callbacks
|
149
|
-
for callback in self.data_callbacks:
|
150
|
-
callback(candle_data, spread_data)
|
151
|
-
|
152
|
-
except Exception as e:
|
153
|
-
print(f"Error calculating spread for {symbol}: {e}")
|
154
|
-
|
155
|
-
def _check_alerts(self, spread_data: Dict):
|
156
|
-
"""Check if spread triggers any alerts."""
|
157
|
-
symbol = spread_data['symbol']
|
158
|
-
spread_bps = spread_data['spread_bps']
|
159
|
-
thresholds = self.alert_thresholds[symbol]
|
160
|
-
|
161
|
-
alert_type = None
|
162
|
-
if spread_bps > thresholds['high']:
|
163
|
-
alert_type = 'HIGH'
|
164
|
-
elif spread_bps < thresholds['low']:
|
165
|
-
alert_type = 'LOW'
|
166
|
-
|
167
|
-
if alert_type:
|
168
|
-
alert_data = {
|
169
|
-
'type': alert_type,
|
170
|
-
'symbol': symbol,
|
171
|
-
'spread_bps': spread_bps,
|
172
|
-
'threshold': thresholds[alert_type.lower()],
|
173
|
-
'timestamp': spread_data['timestamp'],
|
174
|
-
'price': spread_data['price']
|
175
|
-
}
|
176
|
-
|
177
|
-
for callback in self.alert_callbacks:
|
178
|
-
callback(alert_data)
|
179
|
-
|
180
|
-
def _on_error(self, ws, error):
|
181
|
-
"""Handle WebSocket errors."""
|
182
|
-
print(f"WebSocket error: {error}")
|
183
|
-
|
184
|
-
def _on_close(self, ws, close_status_code, close_msg):
|
185
|
-
"""Handle WebSocket connection close."""
|
186
|
-
print("WebSocket connection closed")
|
187
|
-
|
188
|
-
def _on_open(self, ws):
|
189
|
-
"""Handle WebSocket connection open."""
|
190
|
-
print(f"WebSocket connected for symbols: {', '.join(self.symbols)}")
|
191
|
-
|
192
|
-
def start(self):
|
193
|
-
"""Start the live monitoring."""
|
194
|
-
if self.running:
|
195
|
-
print("Monitor is already running")
|
196
|
-
return
|
197
|
-
|
198
|
-
self.running = True
|
199
|
-
|
200
|
-
# Create WebSocket URL
|
201
|
-
ws_url = self._create_websocket_url(self.symbols)
|
202
|
-
|
203
|
-
# Create WebSocket connection
|
204
|
-
self.ws = websocket.WebSocketApp(
|
205
|
-
ws_url,
|
206
|
-
on_message=self._on_message,
|
207
|
-
on_error=self._on_error,
|
208
|
-
on_close=self._on_close,
|
209
|
-
on_open=self._on_open
|
210
|
-
)
|
211
|
-
|
212
|
-
# Start WebSocket in a separate thread
|
213
|
-
self.ws_thread = threading.Thread(target=self.ws.run_forever)
|
214
|
-
self.ws_thread.daemon = True
|
215
|
-
self.ws_thread.start()
|
216
|
-
|
217
|
-
print("Live spread monitoring started...")
|
218
|
-
|
219
|
-
def stop(self):
|
220
|
-
"""Stop the live monitoring."""
|
221
|
-
if not self.running:
|
222
|
-
return
|
223
|
-
|
224
|
-
self.running = False
|
225
|
-
if hasattr(self, 'ws'):
|
226
|
-
self.ws.close()
|
227
|
-
|
228
|
-
print("Live spread monitoring stopped.")
|
229
|
-
|
230
|
-
def get_current_data(self) -> Dict[str, pd.DataFrame]:
|
231
|
-
"""Get current data for all symbols."""
|
232
|
-
result = {}
|
233
|
-
for symbol in self.symbols:
|
234
|
-
if len(self.data_buffers[symbol]) > 0:
|
235
|
-
result[symbol] = pd.DataFrame(list(self.data_buffers[symbol]))
|
236
|
-
return result
|
237
|
-
|
238
|
-
def get_current_spreads(self) -> Dict[str, pd.DataFrame]:
|
239
|
-
"""Get current spread data for all symbols."""
|
240
|
-
result = {}
|
241
|
-
for symbol in self.symbols:
|
242
|
-
if len(self.spread_buffers[symbol]) > 0:
|
243
|
-
result[symbol] = pd.DataFrame(list(self.spread_buffers[symbol]))
|
244
|
-
return result
|
245
|
-
|
246
|
-
def create_live_dashboard_example():
|
247
|
-
"""
|
248
|
-
Example of creating a live dashboard (console-based).
|
249
|
-
"""
|
250
|
-
import time
|
251
|
-
|
252
|
-
def data_callback(candle_data, spread_data):
|
253
|
-
"""Print new data to console."""
|
254
|
-
symbol = spread_data['symbol'].upper()
|
255
|
-
timestamp = spread_data['timestamp'].strftime('%H:%M:%S')
|
256
|
-
price = spread_data['price']
|
257
|
-
spread_bps = spread_data['spread_bps']
|
258
|
-
|
259
|
-
print(f"[{timestamp}] {symbol}: ${price:.2f} | Spread: {spread_bps:.2f}bps")
|
260
|
-
|
261
|
-
def alert_callback(alert_data):
|
262
|
-
"""Print alerts to console."""
|
263
|
-
symbol = alert_data['symbol'].upper()
|
264
|
-
alert_type = alert_data['type']
|
265
|
-
spread_bps = alert_data['spread_bps']
|
266
|
-
threshold = alert_data['threshold']
|
267
|
-
timestamp = alert_data['timestamp'].strftime('%H:%M:%S')
|
268
|
-
|
269
|
-
print(f"🚨 [{timestamp}] {alert_type} SPREAD ALERT for {symbol}: "
|
270
|
-
f"{spread_bps:.2f}bps (threshold: {threshold}bps)")
|
271
|
-
|
272
|
-
# Create monitor
|
273
|
-
monitor = LiveSpreadMonitor(['BTCUSDT', 'ETHUSDT'], window=10)
|
274
|
-
|
275
|
-
# Set custom thresholds
|
276
|
-
monitor.set_alert_threshold('BTCUSDT', high_bps=50, low_bps=2)
|
277
|
-
monitor.set_alert_threshold('ETHUSDT', high_bps=60, low_bps=3)
|
278
|
-
|
279
|
-
# Add callbacks
|
280
|
-
monitor.add_data_callback(data_callback)
|
281
|
-
monitor.add_alert_callback(alert_callback)
|
282
|
-
|
283
|
-
return monitor
|
284
|
-
|
285
|
-
if __name__ == "__main__":
|
286
|
-
print("Live Spread Monitor Example")
|
287
|
-
print("==========================")
|
288
|
-
print("This example demonstrates real-time spread monitoring using WebSockets.")
|
289
|
-
print("Note: This requires an active internet connection and will connect to Binance WebSocket.")
|
290
|
-
print()
|
291
|
-
|
292
|
-
try:
|
293
|
-
# Create and start monitor
|
294
|
-
monitor = create_live_dashboard_example()
|
295
|
-
|
296
|
-
print("Starting live monitor... (Press Ctrl+C to stop)")
|
297
|
-
monitor.start()
|
298
|
-
|
299
|
-
# Run for a demo period
|
300
|
-
time.sleep(60) # Run for 1 minute
|
301
|
-
|
302
|
-
except KeyboardInterrupt:
|
303
|
-
print("\nStopping monitor...")
|
304
|
-
finally:
|
305
|
-
if 'monitor' in locals():
|
306
|
-
monitor.stop()
|
307
|
-
|
308
|
-
print("Example completed.")
|
@@ -1,11 +0,0 @@
|
|
1
|
-
quantjourney_bidask/__init__.py,sha256=ycBwUmX5uZZvnXIygnREyF_VdGQmoXvX-Kkwb0OKegU,298
|
2
|
-
quantjourney_bidask/_version.py,sha256=7lbdg1SKFTzau2oIJGxKQ7-_f5qeY69-EB_gg5wGNI8,220
|
3
|
-
quantjourney_bidask/edge.py,sha256=YdL8So3i9CKQsDm6lI6mNRe-ODhisRhipksq-sfRmuk,6274
|
4
|
-
quantjourney_bidask/edge_expanding.py,sha256=gAdow81VBb2rXtfoEzDur8xvu-rwfo1OQbt3LbUfq4w,2268
|
5
|
-
quantjourney_bidask/edge_rolling.py,sha256=z8463emBLaxa0ceUBk9TPfVayFANo8IeDJ_fuDzGcfA,9103
|
6
|
-
quantjourney_bidask/websocket_fetcher.py,sha256=xMS_qLbSW9hCS3RbNKvkn5HTK0XGmAO4wpaAl4_Mxb4,10895
|
7
|
-
quantjourney_bidask-0.9.4.dist-info/licenses/LICENSE,sha256=m8MEOGnpSBtS6m9z4M9m1JksWWPzu1OK3UgY1wuHf04,1081
|
8
|
-
quantjourney_bidask-0.9.4.dist-info/METADATA,sha256=AeWjnxksyf-aXjdLycosfszLWGX4RhE28wV1q6kuUPw,13202
|
9
|
-
quantjourney_bidask-0.9.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
10
|
-
quantjourney_bidask-0.9.4.dist-info/top_level.txt,sha256=rOBM4GxA87iQv-mR8-WZdu3-Yj5ESyggRICpUhJ-4Dg,20
|
11
|
-
quantjourney_bidask-0.9.4.dist-info/RECORD,,
|
File without changes
|
{quantjourney_bidask-0.9.4.dist-info → quantjourney_bidask-1.0.1.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
File without changes
|