rangebar 11.6.1__cp313-cp313-macosx_11_0_arm64.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.
Files changed (54) hide show
  1. rangebar/CLAUDE.md +327 -0
  2. rangebar/__init__.py +227 -0
  3. rangebar/__init__.pyi +1089 -0
  4. rangebar/_core.cpython-313-darwin.so +0 -0
  5. rangebar/checkpoint.py +472 -0
  6. rangebar/cli.py +298 -0
  7. rangebar/clickhouse/CLAUDE.md +139 -0
  8. rangebar/clickhouse/__init__.py +100 -0
  9. rangebar/clickhouse/bulk_operations.py +309 -0
  10. rangebar/clickhouse/cache.py +734 -0
  11. rangebar/clickhouse/client.py +121 -0
  12. rangebar/clickhouse/config.py +141 -0
  13. rangebar/clickhouse/mixin.py +120 -0
  14. rangebar/clickhouse/preflight.py +504 -0
  15. rangebar/clickhouse/query_operations.py +345 -0
  16. rangebar/clickhouse/schema.sql +187 -0
  17. rangebar/clickhouse/tunnel.py +222 -0
  18. rangebar/constants.py +288 -0
  19. rangebar/conversion.py +177 -0
  20. rangebar/exceptions.py +207 -0
  21. rangebar/exness.py +364 -0
  22. rangebar/hooks.py +311 -0
  23. rangebar/logging.py +171 -0
  24. rangebar/notify/__init__.py +15 -0
  25. rangebar/notify/pushover.py +155 -0
  26. rangebar/notify/telegram.py +271 -0
  27. rangebar/orchestration/__init__.py +20 -0
  28. rangebar/orchestration/count_bounded.py +797 -0
  29. rangebar/orchestration/helpers.py +412 -0
  30. rangebar/orchestration/models.py +76 -0
  31. rangebar/orchestration/precompute.py +498 -0
  32. rangebar/orchestration/range_bars.py +736 -0
  33. rangebar/orchestration/tick_fetcher.py +226 -0
  34. rangebar/ouroboros.py +454 -0
  35. rangebar/processors/__init__.py +22 -0
  36. rangebar/processors/api.py +383 -0
  37. rangebar/processors/core.py +522 -0
  38. rangebar/resource_guard.py +567 -0
  39. rangebar/storage/__init__.py +22 -0
  40. rangebar/storage/checksum_registry.py +218 -0
  41. rangebar/storage/parquet.py +728 -0
  42. rangebar/streaming.py +300 -0
  43. rangebar/validation/__init__.py +69 -0
  44. rangebar/validation/cache_staleness.py +277 -0
  45. rangebar/validation/continuity.py +664 -0
  46. rangebar/validation/gap_classification.py +294 -0
  47. rangebar/validation/post_storage.py +317 -0
  48. rangebar/validation/tier1.py +175 -0
  49. rangebar/validation/tier2.py +261 -0
  50. rangebar-11.6.1.dist-info/METADATA +308 -0
  51. rangebar-11.6.1.dist-info/RECORD +54 -0
  52. rangebar-11.6.1.dist-info/WHEEL +4 -0
  53. rangebar-11.6.1.dist-info/entry_points.txt +2 -0
  54. rangebar-11.6.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,271 @@
1
+ """Telegram notification integration for rangebar-py.
2
+
3
+ This module provides Telegram notifications for hook events, enabling
4
+ "loud" alerts for failures and successes during cache operations.
5
+
6
+ Configuration
7
+ -------------
8
+ Set environment variables (via Doppler or .env):
9
+
10
+ RANGEBAR_TELEGRAM_TOKEN - Bot token from @BotFather
11
+ RANGEBAR_TELEGRAM_CHAT_ID - Your chat ID (send /start to bot to get it)
12
+
13
+ Or use Doppler:
14
+ doppler secrets set RANGEBAR_TELEGRAM_TOKEN --project rangebar --config prd
15
+ doppler secrets set RANGEBAR_TELEGRAM_CHAT_ID --project rangebar --config prd
16
+
17
+ Usage
18
+ -----
19
+ >>> from rangebar.notify.telegram import enable_telegram_notifications
20
+ >>> enable_telegram_notifications()
21
+ >>> # All hook events will now be sent to Telegram
22
+
23
+ Or selective registration for failures only:
24
+ >>> from rangebar.notify.telegram import telegram_notify
25
+ >>> from rangebar.hooks import register_for_failures
26
+ >>> register_for_failures(telegram_notify)
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import json
32
+ import logging
33
+ import os
34
+ from functools import lru_cache
35
+ from typing import TYPE_CHECKING
36
+
37
+ if TYPE_CHECKING:
38
+ from ..hooks import HookPayload
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+ # Default chat ID (Terry Li @EonLabsOperations) - can be overridden via env
43
+ # SSoT-OK: This is a Telegram chat ID, not a version number
44
+ _DEFAULT_CHAT_ID = "90417581"
45
+
46
+
47
+ @lru_cache(maxsize=1)
48
+ def get_telegram_config() -> dict[str, str | None]:
49
+ """Load Telegram configuration from environment.
50
+
51
+ Returns
52
+ -------
53
+ dict
54
+ Configuration with 'token' and 'chat_id' keys.
55
+ Values may be None if not configured.
56
+
57
+ Notes
58
+ -----
59
+ Configuration sources (in priority order):
60
+ 1. Environment variables (RANGEBAR_TELEGRAM_TOKEN, RANGEBAR_TELEGRAM_CHAT_ID)
61
+ 2. Default chat ID (for internal use)
62
+ """
63
+ return {
64
+ "token": os.environ.get("RANGEBAR_TELEGRAM_TOKEN"),
65
+ "chat_id": os.environ.get("RANGEBAR_TELEGRAM_CHAT_ID", _DEFAULT_CHAT_ID),
66
+ }
67
+
68
+
69
+ def is_configured() -> bool:
70
+ """Check if Telegram notifications are configured.
71
+
72
+ Returns
73
+ -------
74
+ bool
75
+ True if both token and chat_id are available.
76
+ """
77
+ config = get_telegram_config()
78
+ return bool(config.get("token") and config.get("chat_id"))
79
+
80
+
81
+ def send_telegram(
82
+ message: str,
83
+ *,
84
+ parse_mode: str = "HTML",
85
+ disable_notification: bool = False,
86
+ ) -> bool:
87
+ """Send a message via Telegram bot.
88
+
89
+ Parameters
90
+ ----------
91
+ message : str
92
+ Message text (supports HTML formatting).
93
+ parse_mode : str
94
+ Telegram parse mode ("HTML" or "Markdown").
95
+ disable_notification : bool
96
+ If True, send silently without notification sound.
97
+
98
+ Returns
99
+ -------
100
+ bool
101
+ True if message was sent successfully, False otherwise.
102
+
103
+ Examples
104
+ --------
105
+ >>> send_telegram("<b>Alert:</b> Cache write failed for BTCUSDT")
106
+ True
107
+ """
108
+ config = get_telegram_config()
109
+ token = config.get("token")
110
+ chat_id = config.get("chat_id")
111
+
112
+ if not token:
113
+ logger.warning("Telegram not configured: RANGEBAR_TELEGRAM_TOKEN not set")
114
+ return False
115
+
116
+ if not chat_id:
117
+ logger.warning("Telegram not configured: RANGEBAR_TELEGRAM_CHAT_ID not set")
118
+ return False
119
+
120
+ # Import requests only when needed (optional dependency)
121
+ try:
122
+ import requests
123
+ except ImportError:
124
+ logger.warning("Telegram notifications require 'requests' package")
125
+ return False
126
+
127
+ url = f"https://api.telegram.org/bot{token}/sendMessage"
128
+ payload = {
129
+ "chat_id": chat_id,
130
+ "text": message,
131
+ "parse_mode": parse_mode,
132
+ "disable_notification": disable_notification,
133
+ }
134
+
135
+ try:
136
+ response = requests.post(url, json=payload, timeout=10)
137
+ response.raise_for_status()
138
+ logger.debug("Telegram message sent successfully")
139
+ return True
140
+ except requests.exceptions.Timeout:
141
+ logger.warning("Telegram notification timed out")
142
+ return False
143
+ except requests.exceptions.RequestException as e:
144
+ logger.warning("Telegram notification failed: %s", e)
145
+ return False
146
+
147
+
148
+ def telegram_notify(payload: HookPayload) -> None:
149
+ """Hook callback that sends notifications to Telegram.
150
+
151
+ This function is designed to be registered as a hook callback.
152
+ It formats the payload into a human-readable message and sends
153
+ it to Telegram.
154
+
155
+ Parameters
156
+ ----------
157
+ payload : HookPayload
158
+ Event payload from the hooks system.
159
+
160
+ Examples
161
+ --------
162
+ >>> from rangebar.hooks import register_hook, HookEvent
163
+ >>> register_hook(HookEvent.CACHE_WRITE_FAILED, telegram_notify)
164
+ """
165
+ emoji = "\u274c" if payload.is_failure else "\u2705" # ❌ or ✅
166
+ status = "FAILED" if payload.is_failure else "SUCCESS"
167
+
168
+ # Format details as readable text
169
+ details_text = ""
170
+ if payload.details:
171
+ details_lines = []
172
+ for key, value in payload.details.items():
173
+ # Handle nested dicts/lists
174
+ if isinstance(value, dict | list):
175
+ display_value = json.dumps(value, indent=2)
176
+ else:
177
+ display_value = value
178
+ details_lines.append(f" {key}: {display_value}")
179
+ details_text = "\n".join(details_lines)
180
+
181
+ message = f"""{emoji} <b>rangebar-py {status}</b>
182
+
183
+ <b>Event:</b> {payload.event.value}
184
+ <b>Symbol:</b> {payload.symbol}
185
+ <b>Time:</b> {payload.timestamp.strftime("%Y-%m-%d %H:%M:%S UTC")}"""
186
+
187
+ if details_text:
188
+ message += f"""
189
+
190
+ <b>Details:</b>
191
+ <pre>{details_text}</pre>"""
192
+
193
+ # Send silently for success, loudly for failures
194
+ send_telegram(
195
+ message,
196
+ disable_notification=not payload.is_failure,
197
+ )
198
+
199
+
200
+ def enable_telegram_notifications() -> bool:
201
+ """Enable Telegram notifications for all hook events.
202
+
203
+ Registers telegram_notify as a callback for all HookEvent types.
204
+
205
+ Returns
206
+ -------
207
+ bool
208
+ True if Telegram is configured and hooks were registered,
209
+ False if Telegram is not configured.
210
+
211
+ Examples
212
+ --------
213
+ >>> if enable_telegram_notifications():
214
+ ... print("Telegram notifications enabled")
215
+ ... else:
216
+ ... print("Telegram not configured - set RANGEBAR_TELEGRAM_TOKEN")
217
+ """
218
+ if not is_configured():
219
+ logger.warning(
220
+ "Telegram not configured. Set RANGEBAR_TELEGRAM_TOKEN environment variable."
221
+ )
222
+ return False
223
+
224
+ from ..hooks import HookEvent, register_hook
225
+
226
+ for event in HookEvent:
227
+ register_hook(event, telegram_notify)
228
+
229
+ logger.info("Telegram notifications enabled for all hook events")
230
+ return True
231
+
232
+
233
+ def enable_failure_notifications() -> bool:
234
+ """Enable Telegram notifications for failure events only.
235
+
236
+ Registers telegram_notify as a callback for all *_FAILED events.
237
+ This is useful when you only want to be alerted about problems.
238
+
239
+ Returns
240
+ -------
241
+ bool
242
+ True if Telegram is configured and hooks were registered,
243
+ False if Telegram is not configured.
244
+
245
+ Examples
246
+ --------
247
+ >>> enable_failure_notifications()
248
+ True
249
+ """
250
+ if not is_configured():
251
+ logger.warning(
252
+ "Telegram not configured. Set RANGEBAR_TELEGRAM_TOKEN environment variable."
253
+ )
254
+ return False
255
+
256
+ from ..hooks import register_for_failures
257
+
258
+ register_for_failures(telegram_notify)
259
+
260
+ logger.info("Telegram notifications enabled for failure events")
261
+ return True
262
+
263
+
264
+ __all__ = [
265
+ "enable_failure_notifications",
266
+ "enable_telegram_notifications",
267
+ "get_telegram_config",
268
+ "is_configured",
269
+ "send_telegram",
270
+ "telegram_notify",
271
+ ]
@@ -0,0 +1,20 @@
1
+ # Issue #46: Modularization M4 - Orchestration subpackage
2
+ """Orchestration subpackage for range bar retrieval and precomputation.
3
+
4
+ Re-exports public symbols for backward compatibility:
5
+ from rangebar.orchestration import get_range_bars, precompute_range_bars
6
+ """
7
+
8
+ from .count_bounded import get_n_range_bars
9
+ from .models import PrecomputeProgress, PrecomputeResult
10
+ from .precompute import precompute_range_bars
11
+ from .range_bars import get_range_bars, get_range_bars_pandas
12
+
13
+ __all__ = [
14
+ "PrecomputeProgress",
15
+ "PrecomputeResult",
16
+ "get_n_range_bars",
17
+ "get_range_bars",
18
+ "get_range_bars_pandas",
19
+ "precompute_range_bars",
20
+ ]