binflow 0.1.0__tar.gz
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.
- binflow-0.1.0/LICENSE +21 -0
- binflow-0.1.0/PKG-INFO +555 -0
- binflow-0.1.0/README.md +526 -0
- binflow-0.1.0/binflow.egg-info/PKG-INFO +555 -0
- binflow-0.1.0/binflow.egg-info/SOURCES.txt +25 -0
- binflow-0.1.0/binflow.egg-info/dependency_links.txt +1 -0
- binflow-0.1.0/binflow.egg-info/requires.txt +5 -0
- binflow-0.1.0/binflow.egg-info/top_level.txt +1 -0
- binflow-0.1.0/market_streaming/__init__.py +63 -0
- binflow-0.1.0/market_streaming/cache.py +172 -0
- binflow-0.1.0/market_streaming/config.py +69 -0
- binflow-0.1.0/market_streaming/exceptions.py +23 -0
- binflow-0.1.0/market_streaming/health.py +81 -0
- binflow-0.1.0/market_streaming/logging_config.py +113 -0
- binflow-0.1.0/market_streaming/manager.py +449 -0
- binflow-0.1.0/market_streaming/models.py +160 -0
- binflow-0.1.0/market_streaming/worker.py +619 -0
- binflow-0.1.0/pyproject.toml +55 -0
- binflow-0.1.0/setup.cfg +4 -0
- binflow-0.1.0/tests/test_cache.py +235 -0
- binflow-0.1.0/tests/test_health.py +89 -0
- binflow-0.1.0/tests/test_load.py +209 -0
- binflow-0.1.0/tests/test_load_orderbook.py +349 -0
- binflow-0.1.0/tests/test_logging.py +116 -0
- binflow-0.1.0/tests/test_manager.py +90 -0
- binflow-0.1.0/tests/test_models.py +192 -0
- binflow-0.1.0/tests/test_worker.py +231 -0
binflow-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nand0san
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
binflow-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: binflow
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Persistent WebSocket engine for Binance market data (Spot, USD-M, COIN-M)
|
|
5
|
+
Author: nand0san
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nand0san/binflow
|
|
8
|
+
Project-URL: Repository, https://github.com/nand0san/binflow
|
|
9
|
+
Project-URL: Issues, https://github.com/nand0san/binflow/issues
|
|
10
|
+
Keywords: binance,websocket,market-data,trading,cryptocurrency,streaming
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: websockets>=13.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# binflow
|
|
31
|
+
|
|
32
|
+
Motor de WebSockets persistente para consumo de datos de mercado en tiempo real desde Binance.
|
|
33
|
+
|
|
34
|
+
Soporta **Spot**, **USD-M Futures** y **COIN-M Futures**. Componente de infraestructura reutilizable que mantiene un cache de mercado vivo en memoria, con reconexion automatica, observabilidad integrada y API sincrona simple.
|
|
35
|
+
|
|
36
|
+
## Caracteristicas
|
|
37
|
+
|
|
38
|
+
- **Multi-mercado** -- Spot, USD-M Futures y COIN-M Futures con un solo parametro
|
|
39
|
+
- **Streaming en tiempo real** de trades y order book desde Binance
|
|
40
|
+
- **Streams combinados** -- multiplexa cientos de simbolos en pocas conexiones WebSocket
|
|
41
|
+
- **Order books profundos** -- hasta 200 niveles por lado via diff depth stream
|
|
42
|
+
- **Cache en memoria** thread-safe con acceso O(1)
|
|
43
|
+
- **Reconexion automatica** con backoff exponencial y jitter
|
|
44
|
+
- **API sincrona** -- se usa desde codigo Python normal, sin `async`/`await`
|
|
45
|
+
- **Observabilidad** -- estado de streams, metricas, deteccion de degradacion
|
|
46
|
+
- **Logging persistente** -- rotacion por tamano, archivo de errores separado, limites de disco
|
|
47
|
+
- **Shutdown limpio** -- sin tareas pendientes ni warnings
|
|
48
|
+
- **Context manager** -- soporte `with` para manejo automatico del ciclo de vida
|
|
49
|
+
- **Probado bajo carga** -- 728 simbolos simultaneos, 60s, cero gaps en trade IDs
|
|
50
|
+
|
|
51
|
+
## Instalacion
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install -e .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
O con dependencias de desarrollo:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install -e ".[dev]"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Requisitos
|
|
64
|
+
|
|
65
|
+
- Python >= 3.11
|
|
66
|
+
- `websockets >= 13.0`
|
|
67
|
+
|
|
68
|
+
## Inicio rapido
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import time
|
|
72
|
+
from market_streaming import MarketStreamManager, StreamConfig
|
|
73
|
+
|
|
74
|
+
config = StreamConfig(
|
|
75
|
+
max_recent_trades=500,
|
|
76
|
+
default_order_book_depth=10,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
with MarketStreamManager(config) as manager:
|
|
80
|
+
manager.add_trade_stream("BTCUSDT")
|
|
81
|
+
manager.add_order_book_stream("BTCUSDT", depth=10)
|
|
82
|
+
|
|
83
|
+
time.sleep(5) # esperar a que lleguen datos
|
|
84
|
+
|
|
85
|
+
# Ultimo trade
|
|
86
|
+
trade = manager.get_last_trade("BTCUSDT")
|
|
87
|
+
if trade:
|
|
88
|
+
print(f"{trade.price} x {trade.quantity}")
|
|
89
|
+
|
|
90
|
+
# Order book
|
|
91
|
+
book = manager.get_order_book("BTCUSDT")
|
|
92
|
+
if book:
|
|
93
|
+
print(f"Mejor bid: {book.bids[0].price}")
|
|
94
|
+
print(f"Mejor ask: {book.asks[0].price}")
|
|
95
|
+
|
|
96
|
+
# Salud del sistema
|
|
97
|
+
print(manager.is_healthy())
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Streams masivos (cientos de simbolos)
|
|
101
|
+
|
|
102
|
+
Para suscribirse a muchos simbolos a la vez, usar `add_trade_streams()` o `add_order_book_streams()`. Estos metodos multiplexan automaticamente los simbolos en conexiones combinadas (hasta 200 por WebSocket), evitando el rate limit de conexiones de Binance:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT", ...] # cientos de simbolos
|
|
106
|
+
|
|
107
|
+
with MarketStreamManager(config) as manager:
|
|
108
|
+
# Registra todos los simbolos en pocas conexiones combinadas
|
|
109
|
+
manager.add_trade_streams(symbols)
|
|
110
|
+
|
|
111
|
+
time.sleep(10)
|
|
112
|
+
|
|
113
|
+
for symbol in symbols:
|
|
114
|
+
trade = manager.get_last_trade(symbol)
|
|
115
|
+
if trade:
|
|
116
|
+
print(f"{symbol}: {trade.price}")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Order books profundos
|
|
120
|
+
|
|
121
|
+
Para acumular mas de 20 niveles, configurar `default_order_book_depth` > 20. El sistema usara automaticamente el diff depth stream de Binance:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
config = StreamConfig(default_order_book_depth=200)
|
|
125
|
+
|
|
126
|
+
with MarketStreamManager(config) as manager:
|
|
127
|
+
manager.add_order_book_streams(["BTCUSDT", "ETHUSDT"], depth=200)
|
|
128
|
+
time.sleep(60) # acumular niveles
|
|
129
|
+
|
|
130
|
+
book = manager.get_order_book("BTCUSDT")
|
|
131
|
+
print(f"Niveles bid: {len(book.bids)}, ask: {len(book.asks)}")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
> **Nota**: profundidades de 5, 10 y 20 usan el Partial Book Depth Stream (snapshots completos). Cualquier otro valor usa el Diff Depth Stream (actualizaciones incrementales), que acumula niveles con el tiempo.
|
|
135
|
+
|
|
136
|
+
### Futuros USD-M
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from market_streaming import MarketStreamManager, StreamConfig, Market
|
|
140
|
+
|
|
141
|
+
config = StreamConfig(market=Market.FUTURES_USD)
|
|
142
|
+
|
|
143
|
+
with MarketStreamManager(config) as manager:
|
|
144
|
+
manager.add_trade_stream("BTCUSDT")
|
|
145
|
+
manager.add_order_book_stream("BTCUSDT")
|
|
146
|
+
# ...
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Futuros COIN-M
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from market_streaming import StreamConfig, Market
|
|
153
|
+
|
|
154
|
+
config = StreamConfig(market=Market.FUTURES_COIN)
|
|
155
|
+
# Los pares de COIN-M usan formato distinto (ej: BTCUSD_PERP)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## API publica
|
|
159
|
+
|
|
160
|
+
### MarketStreamManager
|
|
161
|
+
|
|
162
|
+
Fachada principal del sistema. Gestiona streams, expone datos del cache y metricas.
|
|
163
|
+
|
|
164
|
+
#### Ciclo de vida
|
|
165
|
+
|
|
166
|
+
| Metodo | Descripcion |
|
|
167
|
+
|--------|-------------|
|
|
168
|
+
| `start()` | Arranca el motor en un thread dedicado |
|
|
169
|
+
| `stop()` | Detiene todos los streams y libera recursos |
|
|
170
|
+
|
|
171
|
+
Soporta context manager (`with MarketStreamManager(config) as mgr:`).
|
|
172
|
+
|
|
173
|
+
#### Registro de streams individuales
|
|
174
|
+
|
|
175
|
+
| Metodo | Descripcion |
|
|
176
|
+
|--------|-------------|
|
|
177
|
+
| `add_trade_stream(symbol)` | Suscribe al stream de trades de un simbolo |
|
|
178
|
+
| `add_order_book_stream(symbol, depth=20)` | Suscribe al stream de profundidad de un simbolo |
|
|
179
|
+
| `remove_stream(stream_id)` | Detiene y elimina un stream |
|
|
180
|
+
|
|
181
|
+
Cada metodo devuelve un `stream_id` (ej: `trade_BTCUSDT`, `depth_BTCUSDT`).
|
|
182
|
+
|
|
183
|
+
#### Registro de streams masivos (combinados)
|
|
184
|
+
|
|
185
|
+
| Metodo | Descripcion |
|
|
186
|
+
|--------|-------------|
|
|
187
|
+
| `add_trade_streams(symbols, batch_size=200)` | Registra trades para multiples simbolos en conexiones combinadas |
|
|
188
|
+
| `add_order_book_streams(symbols, depth=None, batch_size=200)` | Registra order books para multiples simbolos en conexiones combinadas |
|
|
189
|
+
|
|
190
|
+
Estos metodos crean `CombinedStreamWorker`s que multiplexan hasta `batch_size` simbolos por conexion WebSocket usando el endpoint `/stream?streams=...` de Binance. Devuelven una lista de worker IDs.
|
|
191
|
+
|
|
192
|
+
#### Consulta de datos
|
|
193
|
+
|
|
194
|
+
| Metodo | Retorno |
|
|
195
|
+
|--------|---------|
|
|
196
|
+
| `get_last_trade(symbol)` | `Trade` o `None` |
|
|
197
|
+
| `get_recent_trades(symbol, limit=100)` | `list[Trade]` |
|
|
198
|
+
| `get_order_book(symbol)` | `OrderBookSnapshot` o `None` |
|
|
199
|
+
|
|
200
|
+
Los simbolos son case-insensitive: `"btcusdt"` y `"BTCUSDT"` son equivalentes.
|
|
201
|
+
|
|
202
|
+
#### Observabilidad
|
|
203
|
+
|
|
204
|
+
| Metodo | Descripcion |
|
|
205
|
+
|--------|-------------|
|
|
206
|
+
| `get_stream_status(stream_id)` | Estado de un stream individual |
|
|
207
|
+
| `get_all_streams_status()` | Estado de todos los streams |
|
|
208
|
+
| `get_health()` | Diagnostico de salud global (`SystemHealth`) |
|
|
209
|
+
| `get_stats()` | Estadisticas resumidas (dict) |
|
|
210
|
+
| `is_healthy()` | `True` si todos los streams estan sanos |
|
|
211
|
+
| `print_summary()` | Imprime resumen por logging |
|
|
212
|
+
|
|
213
|
+
### Modelos de datos
|
|
214
|
+
|
|
215
|
+
#### Trade
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
@dataclass(frozen=True)
|
|
219
|
+
class Trade:
|
|
220
|
+
symbol: str # par de trading (ej: "BTCUSDT")
|
|
221
|
+
trade_id: int # ID unico del trade en Binance
|
|
222
|
+
price: float # precio de ejecucion
|
|
223
|
+
quantity: float # cantidad ejecutada
|
|
224
|
+
quote_quantity: float # cantidad en moneda cotizada
|
|
225
|
+
buyer_maker: bool # True si el comprador es el maker (trade = venta)
|
|
226
|
+
timestamp_ms: int # timestamp del trade en milisegundos
|
|
227
|
+
price_str: str # precio como cadena original (precision completa)
|
|
228
|
+
quantity_str: str # cantidad como cadena original
|
|
229
|
+
quote_quantity_str: str
|
|
230
|
+
is_best_match: bool # solo Spot
|
|
231
|
+
event_time_ms: int # timestamp del evento WebSocket
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Incluye `to_rest_format()` que devuelve un dict compatible con GET /api/v3/trades.
|
|
235
|
+
|
|
236
|
+
#### OrderBookSnapshot
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
@dataclass
|
|
240
|
+
class OrderBookSnapshot:
|
|
241
|
+
symbol: str
|
|
242
|
+
bids: list[OrderBookLevel] # ordenados precio descendente (mejor bid primero)
|
|
243
|
+
asks: list[OrderBookLevel] # ordenados precio ascendente (mejor ask primero)
|
|
244
|
+
last_update_id: int # ID de la ultima actualizacion aplicada
|
|
245
|
+
timestamp: float # time.time() de la ultima actualizacion
|
|
246
|
+
event_time_ms: int
|
|
247
|
+
transaction_time_ms: int
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### OrderBookLevel
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
@dataclass(frozen=True)
|
|
254
|
+
class OrderBookLevel:
|
|
255
|
+
price: float
|
|
256
|
+
quantity: float
|
|
257
|
+
price_str: str # cadena original de Binance
|
|
258
|
+
quantity_str: str
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Incluye `to_rest_format()` que devuelve `[price_str, quantity_str]` compatible con la REST API.
|
|
262
|
+
|
|
263
|
+
#### StreamState
|
|
264
|
+
|
|
265
|
+
Estados posibles de un stream:
|
|
266
|
+
|
|
267
|
+
| Estado | Significado |
|
|
268
|
+
|--------|-------------|
|
|
269
|
+
| `starting` | Iniciando conexion |
|
|
270
|
+
| `running` | Conectado y recibiendo datos |
|
|
271
|
+
| `reconnecting` | Reconectando tras un error |
|
|
272
|
+
| `stale` | Conectado pero sin datos recientes |
|
|
273
|
+
| `stopped` | Detenido de forma limpia |
|
|
274
|
+
| `failed` | Error fatal, no reconectable |
|
|
275
|
+
|
|
276
|
+
#### SystemHealth
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
@dataclass
|
|
280
|
+
class SystemHealth:
|
|
281
|
+
is_healthy: bool # True si todos los streams estan sanos
|
|
282
|
+
total_streams: int
|
|
283
|
+
running_streams: int
|
|
284
|
+
stale_streams: int
|
|
285
|
+
reconnecting_streams: int
|
|
286
|
+
stopped_streams: int
|
|
287
|
+
uptime_s: float # segundos desde el arranque
|
|
288
|
+
total_messages: int # total de mensajes procesados
|
|
289
|
+
stream_details: dict[str, StreamStatus]
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Configuracion
|
|
293
|
+
|
|
294
|
+
Toda la configuracion se centraliza en `StreamConfig`:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from market_streaming import StreamConfig, Market
|
|
298
|
+
|
|
299
|
+
config = StreamConfig(
|
|
300
|
+
# Mercado (default: Spot)
|
|
301
|
+
market=Market.SPOT, # SPOT | FUTURES_USD | FUTURES_COIN
|
|
302
|
+
# base_url="wss://...", # si se pasa, tiene prioridad sobre market
|
|
303
|
+
|
|
304
|
+
# Conexion
|
|
305
|
+
ping_interval_s=20.0,
|
|
306
|
+
ping_timeout_s=10.0,
|
|
307
|
+
|
|
308
|
+
# Cache
|
|
309
|
+
max_recent_trades=1000,
|
|
310
|
+
default_order_book_depth=20, # >20 usa diff depth stream
|
|
311
|
+
|
|
312
|
+
# Reconexion
|
|
313
|
+
reconnect_base_delay_s=1.0,
|
|
314
|
+
reconnect_max_delay_s=60.0,
|
|
315
|
+
reconnect_jitter_factor=0.2,
|
|
316
|
+
max_reconnect_attempts=0, # 0 = sin limite
|
|
317
|
+
|
|
318
|
+
# Health
|
|
319
|
+
stale_threshold_s=30.0,
|
|
320
|
+
|
|
321
|
+
# Logging
|
|
322
|
+
log_level=logging.INFO,
|
|
323
|
+
log_dir="logs",
|
|
324
|
+
log_max_bytes=10 * 1024 * 1024, # 10 MB por archivo
|
|
325
|
+
log_backup_count=5, # max 5 archivos rotados
|
|
326
|
+
)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Mercados soportados
|
|
330
|
+
|
|
331
|
+
| Market | URL WebSocket | Ejemplo de par |
|
|
332
|
+
|--------|---------------|----------------|
|
|
333
|
+
| `Market.SPOT` | `wss://stream.binance.com:9443/ws` | `BTCUSDT` |
|
|
334
|
+
| `Market.FUTURES_USD` | `wss://fstream.binance.com/ws` | `BTCUSDT` |
|
|
335
|
+
| `Market.FUTURES_COIN` | `wss://dstream.binance.com/ws` | `BTCUSD_PERP` |
|
|
336
|
+
|
|
337
|
+
Todos los campos tienen valores por defecto razonables. `StreamConfig()` sin argumentos conecta a Spot.
|
|
338
|
+
|
|
339
|
+
## Logging
|
|
340
|
+
|
|
341
|
+
El sistema genera dos archivos de log con rotacion automatica:
|
|
342
|
+
|
|
343
|
+
| Archivo | Nivel | Uso |
|
|
344
|
+
|---------|-------|-----|
|
|
345
|
+
| `binflow.log` | DEBUG+ | Analisis completo, debug, tracing |
|
|
346
|
+
| `binflow.error.log` | WARNING+ | Deteccion rapida de fallos e interrupciones |
|
|
347
|
+
|
|
348
|
+
### Rotacion y limites de disco
|
|
349
|
+
|
|
350
|
+
- Cada archivo rota al alcanzar `log_max_bytes` (default: 10 MB)
|
|
351
|
+
- Se mantienen como maximo `log_backup_count` archivos rotados (default: 5)
|
|
352
|
+
- Espacio maximo en disco: ~120 MB (2 archivos x 6 versiones x 10 MB)
|
|
353
|
+
- Los archivos rotados se nombran `binflow.log.1`, `binflow.log.2`, etc.
|
|
354
|
+
|
|
355
|
+
### Formato de archivo
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
2026-03-06 20:15:32 | INFO | market_streaming.worker:_connect_and_consume:178 | Stream trade_BTCUSDT: conexion establecida
|
|
359
|
+
2026-03-06 20:15:33 | WARNING | market_streaming.worker:_run:123 | Error en stream depth_BTCUSDT (intento 1): [Errno 111] Connection refused
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Configuracion de logging
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
import logging
|
|
366
|
+
from market_streaming import StreamConfig
|
|
367
|
+
|
|
368
|
+
# Produccion: solo errores en consola, todo en disco
|
|
369
|
+
config = StreamConfig(
|
|
370
|
+
log_level=logging.ERROR,
|
|
371
|
+
log_dir="logs",
|
|
372
|
+
log_max_bytes=10 * 1024 * 1024,
|
|
373
|
+
log_backup_count=5,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Desarrollo: verbose en consola, sin disco
|
|
377
|
+
config = StreamConfig(
|
|
378
|
+
log_level=logging.DEBUG,
|
|
379
|
+
log_dir="", # desactiva archivos en disco
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Directorio personalizado
|
|
383
|
+
config = StreamConfig(log_dir="/var/log/binflow")
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Arquitectura
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
MarketStreamManager API publica sincrona (fachada)
|
|
390
|
+
|
|
|
391
|
+
+-- threading.Thread Thread dedicado con event loop asyncio
|
|
392
|
+
| |
|
|
393
|
+
| +-- WebSocketStreamWorker (trade_BTCUSDT) 1 simbolo / conexion
|
|
394
|
+
| +-- WebSocketStreamWorker (depth_BTCUSDT) 1 simbolo / conexion
|
|
395
|
+
| +-- CombinedStreamWorker (combined_trades_0) hasta 200 simbolos / conexion
|
|
396
|
+
| +-- CombinedStreamWorker (combined_depth_0) hasta 200 simbolos / conexion
|
|
397
|
+
| +-- ...
|
|
398
|
+
|
|
|
399
|
+
+-- MarketDataCache Cache en memoria (threading.Lock)
|
|
400
|
+
|
|
|
401
|
+
+-- HealthMonitor Evaluacion de salud
|
|
402
|
+
|
|
|
403
|
+
+-- Logging RotatingFileHandler (general + errores)
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Workers
|
|
407
|
+
|
|
408
|
+
| Worker | Uso | Conexiones |
|
|
409
|
+
|--------|-----|-----------|
|
|
410
|
+
| `WebSocketStreamWorker` | 1 simbolo por conexion (`add_trade_stream`, `add_order_book_stream`) | 1 WS por stream |
|
|
411
|
+
| `CombinedStreamWorker` | Hasta 200 simbolos por conexion (`add_trade_streams`, `add_order_book_streams`) | 1 WS por batch |
|
|
412
|
+
|
|
413
|
+
Los workers combinados usan el endpoint `/stream?streams=sym1@trade/sym2@trade/...` de Binance y desenvuelven el formato `{stream, data}` de los mensajes.
|
|
414
|
+
|
|
415
|
+
### Modelo de concurrencia
|
|
416
|
+
|
|
417
|
+
El sistema usa un **thread dedicado con event loop asyncio**:
|
|
418
|
+
|
|
419
|
+
- **API publica sincrona**: el codigo cliente llama `manager.start()`, `manager.get_last_trade()`, etc. sin `await`. Esto permite usar el motor desde cualquier contexto Python.
|
|
420
|
+
- **Event loop interno**: un thread daemon ejecuta el loop asyncio donde corren todos los workers WebSocket de forma concurrente.
|
|
421
|
+
- **Cache thread-safe**: `threading.Lock` protege las escrituras (desde el thread asyncio) y las lecturas (desde el thread principal). El contention es despreciable porque las operaciones son O(1).
|
|
422
|
+
|
|
423
|
+
### Reconexion
|
|
424
|
+
|
|
425
|
+
Cada worker gestiona su propia reconexion:
|
|
426
|
+
|
|
427
|
+
1. Error de conexion o desconexion detectada
|
|
428
|
+
2. Backoff exponencial: `min(base * 2^n, max_delay)`
|
|
429
|
+
3. Jitter aleatorio de +-20% sobre el delay calculado
|
|
430
|
+
4. Contador de reconexiones incrementado
|
|
431
|
+
5. Si se supera `max_reconnect_attempts` (y no es 0), el stream pasa a `failed`
|
|
432
|
+
6. Errores fatales (ej: stream invalido) no reconectan
|
|
433
|
+
|
|
434
|
+
### Depth streams
|
|
435
|
+
|
|
436
|
+
| Profundidad | Stream | Tipo de mensaje |
|
|
437
|
+
|-------------|--------|-----------------|
|
|
438
|
+
| 5, 10, 20 | `symbol@depth{N}` (Partial Book Depth) | Snapshots completos cada ~1s |
|
|
439
|
+
| Cualquier otro valor | `symbol@depth` (Diff Depth) | Actualizaciones incrementales |
|
|
440
|
+
|
|
441
|
+
El diff depth stream acumula niveles con el tiempo. Para un order book completo desde el primer momento, usar profundidades de 5, 10 o 20.
|
|
442
|
+
|
|
443
|
+
### Cache en memoria
|
|
444
|
+
|
|
445
|
+
| Dato | Estructura | Acceso |
|
|
446
|
+
|------|-----------|--------|
|
|
447
|
+
| Ultimo trade por simbolo | `dict[symbol, Trade]` | O(1) |
|
|
448
|
+
| Trades recientes por simbolo | `dict[symbol, deque(maxlen=N)]` | O(1) amortizado |
|
|
449
|
+
| Order book por simbolo | `dict[symbol, OrderBookSnapshot]` | O(1) |
|
|
450
|
+
|
|
451
|
+
El order book soporta tanto snapshots completos como actualizaciones incrementales con eliminacion de niveles con cantidad cero.
|
|
452
|
+
|
|
453
|
+
## Estructura del proyecto
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
market_streaming/
|
|
457
|
+
__init__.py Re-exports de la API publica
|
|
458
|
+
config.py StreamConfig, Market, MARKET_URLS
|
|
459
|
+
models.py Trade, OrderBookSnapshot, StreamStatus, SystemHealth
|
|
460
|
+
exceptions.py Excepciones del dominio
|
|
461
|
+
cache.py MarketDataCache
|
|
462
|
+
worker.py WebSocketStreamWorker, CombinedStreamWorker
|
|
463
|
+
health.py HealthMonitor
|
|
464
|
+
manager.py MarketStreamManager
|
|
465
|
+
logging_config.py Setup de logging con rotacion
|
|
466
|
+
tests/
|
|
467
|
+
test_cache.py 18 tests - trades, order book, continuidad
|
|
468
|
+
test_models.py 16 tests - inmutabilidad, defaults, config, mercados
|
|
469
|
+
test_health.py 8 tests - deteccion stale, salud global
|
|
470
|
+
test_worker.py 12 tests - URLs por mercado, parsing mensajes, backoff
|
|
471
|
+
test_manager.py 13 tests - lifecycle, streams, observabilidad
|
|
472
|
+
test_logging.py 13 tests - handlers, rotacion, archivos, teardown
|
|
473
|
+
test_load.py 8 tests - carga: 728 symbols trades, 60s
|
|
474
|
+
test_load_orderbook.py 13 tests - carga: 728 symbols order books profundos
|
|
475
|
+
examples/
|
|
476
|
+
inspect_trade.py Estructura del Trade: campos, tipos, precision, formato REST
|
|
477
|
+
inspect_order_book.py OrderBookSnapshot: estructura, niveles, formato REST
|
|
478
|
+
inspect_config.py StreamConfig: campos, Market enum, URLs (sin conexion)
|
|
479
|
+
inspect_health.py SystemHealth, StreamStatus, StreamState
|
|
480
|
+
live_trades.py Feed de trades en tiempo real
|
|
481
|
+
live_order_book.py Order book visual con barras de volumen y spread
|
|
482
|
+
multi_market.py Tabla comparativa multi-simbolo en tiempo real
|
|
483
|
+
notebooks/
|
|
484
|
+
streaming_demo.ipynb Demo completa: stream, inspeccion, acumulacion, verificacion
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Ejemplos
|
|
488
|
+
|
|
489
|
+
Todos los ejemplos se ejecutan con `python examples/<nombre>.py`.
|
|
490
|
+
|
|
491
|
+
### Inspeccion de datos (sin conexion)
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
python examples/inspect_trade.py # estructura Trade
|
|
495
|
+
python examples/inspect_order_book.py # estructura OrderBookSnapshot
|
|
496
|
+
python examples/inspect_config.py # StreamConfig, Market, URLs
|
|
497
|
+
python examples/inspect_health.py # SystemHealth, StreamStatus
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Streams en vivo
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
python examples/live_trades.py # feed de trades BTCUSDT en tiempo real
|
|
504
|
+
python examples/live_order_book.py # order book visual con spread
|
|
505
|
+
python examples/multi_market.py # tabla comparativa multi-simbolo
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Notebook
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
jupyter notebook notebooks/streaming_demo.ipynb
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Demo interactiva: arranque, inspeccion, acumulacion de 2 minutos con verificacion de continuidad, estadisticas y shutdown limpio.
|
|
515
|
+
|
|
516
|
+
## Tests
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
# Ejecutar todos los tests unitarios (83 tests, <1s)
|
|
520
|
+
pytest tests/ --ignore=tests/test_load.py --ignore=tests/test_load_orderbook.py -v
|
|
521
|
+
|
|
522
|
+
# Tests de carga contra Binance real (~70s cada uno)
|
|
523
|
+
pytest tests/test_load.py -v -s # 728 symbols, trades, 60s
|
|
524
|
+
pytest tests/test_load_orderbook.py -v -s # 728 symbols, depth 200, 60s
|
|
525
|
+
|
|
526
|
+
# Todos los tests (102 tests)
|
|
527
|
+
pytest tests/ -v -s
|
|
528
|
+
|
|
529
|
+
# Un test especifico
|
|
530
|
+
pytest tests/test_cache.py::TestTradeCache::test_trade_ids_continuous -v
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Pruebas de carga
|
|
534
|
+
|
|
535
|
+
Los tests de carga (`test_load.py`, `test_load_orderbook.py`) conectan a Binance real y verifican:
|
|
536
|
+
|
|
537
|
+
- **Continuidad**: trade IDs consecutivos sin huecos
|
|
538
|
+
- **Unicidad**: cero duplicados
|
|
539
|
+
- **Orden**: IDs estrictamente crecientes
|
|
540
|
+
- **Timestamps**: no decrecientes
|
|
541
|
+
- **Profundidad**: order books de hasta 200 niveles por lado
|
|
542
|
+
- **Integridad estructural**: bids descendentes, asks ascendentes, spread positivo
|
|
543
|
+
- **Precision**: cadenas originales de Binance preservadas
|
|
544
|
+
- **Estabilidad**: todos los workers running, cero reconexiones
|
|
545
|
+
|
|
546
|
+
## Limitaciones
|
|
547
|
+
|
|
548
|
+
- No implementa el flujo completo de sincronizacion del order book de Binance (REST snapshot + WebSocket diff). Para profundidades > 20, el diff depth stream acumula niveles incrementalmente sin snapshot REST inicial.
|
|
549
|
+
- No persiste datos en disco. Si el proceso muere, se pierde el cache.
|
|
550
|
+
- Las metricas son in-process. No hay exportacion a Prometheus, StatsD u otros sistemas externos.
|
|
551
|
+
- No implementa autenticacion para streams privados (user data streams).
|
|
552
|
+
|
|
553
|
+
## Licencia
|
|
554
|
+
|
|
555
|
+
MIT
|