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.
- rangebar/CLAUDE.md +327 -0
- rangebar/__init__.py +227 -0
- rangebar/__init__.pyi +1089 -0
- rangebar/_core.cpython-313-darwin.so +0 -0
- rangebar/checkpoint.py +472 -0
- rangebar/cli.py +298 -0
- rangebar/clickhouse/CLAUDE.md +139 -0
- rangebar/clickhouse/__init__.py +100 -0
- rangebar/clickhouse/bulk_operations.py +309 -0
- rangebar/clickhouse/cache.py +734 -0
- rangebar/clickhouse/client.py +121 -0
- rangebar/clickhouse/config.py +141 -0
- rangebar/clickhouse/mixin.py +120 -0
- rangebar/clickhouse/preflight.py +504 -0
- rangebar/clickhouse/query_operations.py +345 -0
- rangebar/clickhouse/schema.sql +187 -0
- rangebar/clickhouse/tunnel.py +222 -0
- rangebar/constants.py +288 -0
- rangebar/conversion.py +177 -0
- rangebar/exceptions.py +207 -0
- rangebar/exness.py +364 -0
- rangebar/hooks.py +311 -0
- rangebar/logging.py +171 -0
- rangebar/notify/__init__.py +15 -0
- rangebar/notify/pushover.py +155 -0
- rangebar/notify/telegram.py +271 -0
- rangebar/orchestration/__init__.py +20 -0
- rangebar/orchestration/count_bounded.py +797 -0
- rangebar/orchestration/helpers.py +412 -0
- rangebar/orchestration/models.py +76 -0
- rangebar/orchestration/precompute.py +498 -0
- rangebar/orchestration/range_bars.py +736 -0
- rangebar/orchestration/tick_fetcher.py +226 -0
- rangebar/ouroboros.py +454 -0
- rangebar/processors/__init__.py +22 -0
- rangebar/processors/api.py +383 -0
- rangebar/processors/core.py +522 -0
- rangebar/resource_guard.py +567 -0
- rangebar/storage/__init__.py +22 -0
- rangebar/storage/checksum_registry.py +218 -0
- rangebar/storage/parquet.py +728 -0
- rangebar/streaming.py +300 -0
- rangebar/validation/__init__.py +69 -0
- rangebar/validation/cache_staleness.py +277 -0
- rangebar/validation/continuity.py +664 -0
- rangebar/validation/gap_classification.py +294 -0
- rangebar/validation/post_storage.py +317 -0
- rangebar/validation/tier1.py +175 -0
- rangebar/validation/tier2.py +261 -0
- rangebar-11.6.1.dist-info/METADATA +308 -0
- rangebar-11.6.1.dist-info/RECORD +54 -0
- rangebar-11.6.1.dist-info/WHEEL +4 -0
- rangebar-11.6.1.dist-info/entry_points.txt +2 -0
- rangebar-11.6.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""ClickHouse client utilities for rangebar cache.
|
|
2
|
+
|
|
3
|
+
This module provides client creation and exception handling.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
import clickhouse_connect
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ClickHouseUnavailableError(RuntimeError):
|
|
15
|
+
"""ClickHouse not available at specified host:port.
|
|
16
|
+
|
|
17
|
+
Raised when connection to ClickHouse fails. Includes actionable guidance.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ClickHouseQueryError(RuntimeError):
|
|
22
|
+
"""Query execution failed.
|
|
23
|
+
|
|
24
|
+
Raised when a ClickHouse query fails to execute.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_client(
|
|
29
|
+
host: str = "localhost",
|
|
30
|
+
port: int = 8123,
|
|
31
|
+
database: str = "default",
|
|
32
|
+
**kwargs: Any,
|
|
33
|
+
) -> clickhouse_connect.driver.Client:
|
|
34
|
+
"""Get a ClickHouse client connection.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
host : str
|
|
39
|
+
Host to connect to (default: localhost)
|
|
40
|
+
port : int
|
|
41
|
+
HTTP port (default: 8123)
|
|
42
|
+
database : str
|
|
43
|
+
Database to use (default: default)
|
|
44
|
+
**kwargs
|
|
45
|
+
Additional arguments passed to clickhouse_connect.get_client()
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
clickhouse_connect.driver.Client
|
|
50
|
+
Connected ClickHouse client
|
|
51
|
+
|
|
52
|
+
Raises
|
|
53
|
+
------
|
|
54
|
+
ClickHouseUnavailableError
|
|
55
|
+
If connection fails
|
|
56
|
+
|
|
57
|
+
Examples
|
|
58
|
+
--------
|
|
59
|
+
>>> client = get_client()
|
|
60
|
+
>>> version = client.command("SELECT version()")
|
|
61
|
+
>>> client.close()
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
import clickhouse_connect
|
|
65
|
+
|
|
66
|
+
client = clickhouse_connect.get_client(
|
|
67
|
+
host=host,
|
|
68
|
+
port=port,
|
|
69
|
+
database=database,
|
|
70
|
+
**kwargs,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Verify connection with a simple query
|
|
74
|
+
client.command("SELECT 1")
|
|
75
|
+
return client
|
|
76
|
+
|
|
77
|
+
except ImportError as e:
|
|
78
|
+
msg = (
|
|
79
|
+
"clickhouse-connect not installed. "
|
|
80
|
+
"Install with: pip install clickhouse-connect"
|
|
81
|
+
)
|
|
82
|
+
raise ClickHouseUnavailableError(msg) from e
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
msg = f"Failed to connect to ClickHouse at {host}:{port}: {e}"
|
|
86
|
+
raise ClickHouseUnavailableError(msg) from e
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def execute_query(
|
|
90
|
+
client: clickhouse_connect.driver.Client,
|
|
91
|
+
query: str,
|
|
92
|
+
parameters: dict[str, Any] | None = None,
|
|
93
|
+
) -> Any:
|
|
94
|
+
"""Execute a ClickHouse query with error handling.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
client : Client
|
|
99
|
+
ClickHouse client
|
|
100
|
+
query : str
|
|
101
|
+
SQL query to execute
|
|
102
|
+
parameters : dict, optional
|
|
103
|
+
Query parameters
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
Any
|
|
108
|
+
Query result
|
|
109
|
+
|
|
110
|
+
Raises
|
|
111
|
+
------
|
|
112
|
+
ClickHouseQueryError
|
|
113
|
+
If query execution fails
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
if parameters:
|
|
117
|
+
return client.command(query, parameters=parameters)
|
|
118
|
+
return client.command(query)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
msg = f"Query failed: {e}\nQuery: {query[:200]}..."
|
|
121
|
+
raise ClickHouseQueryError(msg) from e
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""ClickHouse configuration for rangebar cache.
|
|
2
|
+
|
|
3
|
+
This module provides configuration management following the mise SSoT pattern.
|
|
4
|
+
All host information comes from environment variables or ~/.ssh/config aliases,
|
|
5
|
+
never hardcoded in the package.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ClickHouseConfigError(ValueError):
|
|
16
|
+
"""Configuration error for ClickHouse connection."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ConnectionMode(str, Enum):
|
|
20
|
+
"""Connection mode for ClickHouse cache.
|
|
21
|
+
|
|
22
|
+
Controls how rangebar-py connects to ClickHouse:
|
|
23
|
+
- LOCAL: Force localhost:8123 only (no SSH aliases)
|
|
24
|
+
- CLOUD: Require CLICKHOUSE_HOST environment variable
|
|
25
|
+
- AUTO: Auto-detect (try localhost first, then SSH aliases)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
LOCAL = "local"
|
|
29
|
+
CLOUD = "cloud"
|
|
30
|
+
AUTO = "auto"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_connection_mode() -> ConnectionMode:
|
|
34
|
+
"""Get the connection mode from environment.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
ConnectionMode
|
|
39
|
+
Current connection mode based on RANGEBAR_MODE env var.
|
|
40
|
+
Defaults to AUTO if not set.
|
|
41
|
+
|
|
42
|
+
Examples
|
|
43
|
+
--------
|
|
44
|
+
>>> import os
|
|
45
|
+
>>> os.environ["RANGEBAR_MODE"] = "local"
|
|
46
|
+
>>> get_connection_mode()
|
|
47
|
+
<ConnectionMode.LOCAL: 'local'>
|
|
48
|
+
"""
|
|
49
|
+
mode_str = os.getenv("RANGEBAR_MODE", "auto").lower()
|
|
50
|
+
try:
|
|
51
|
+
return ConnectionMode(mode_str)
|
|
52
|
+
except ValueError:
|
|
53
|
+
# Invalid mode, default to AUTO
|
|
54
|
+
return ConnectionMode.AUTO
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class ClickHouseConfig:
|
|
59
|
+
"""Configuration for ClickHouse connection.
|
|
60
|
+
|
|
61
|
+
Follows the mise SSoT pattern - all values come from environment variables.
|
|
62
|
+
Host aliases reference ~/.ssh/config entries, never actual IPs/hostnames.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
host : str
|
|
67
|
+
Host to connect to (default: localhost)
|
|
68
|
+
port : int
|
|
69
|
+
Port number (default: 8123)
|
|
70
|
+
database : str
|
|
71
|
+
Database name (default: rangebar_cache)
|
|
72
|
+
mode : ConnectionMode
|
|
73
|
+
Connection mode (default: AUTO)
|
|
74
|
+
|
|
75
|
+
Environment Variables
|
|
76
|
+
---------------------
|
|
77
|
+
RANGEBAR_MODE : str
|
|
78
|
+
Connection mode: "local", "cloud", or "auto" (default: auto)
|
|
79
|
+
RANGEBAR_CH_HOSTS : str
|
|
80
|
+
Comma-separated list of SSH aliases from ~/.ssh/config
|
|
81
|
+
RANGEBAR_CH_PRIMARY : str
|
|
82
|
+
Primary host alias to prefer
|
|
83
|
+
CLICKHOUSE_HOST : str
|
|
84
|
+
Direct host override (for localhost only)
|
|
85
|
+
CLICKHOUSE_PORT : str
|
|
86
|
+
Port override
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
host: str = field(default_factory=lambda: os.getenv("CLICKHOUSE_HOST", "localhost"))
|
|
90
|
+
port: int = field(default_factory=lambda: int(os.getenv("CLICKHOUSE_PORT", "8123")))
|
|
91
|
+
database: str = "rangebar_cache"
|
|
92
|
+
mode: ConnectionMode = field(default_factory=get_connection_mode)
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_env(cls) -> ClickHouseConfig:
|
|
96
|
+
"""Create configuration from environment variables.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
ClickHouseConfig
|
|
101
|
+
Configuration instance
|
|
102
|
+
|
|
103
|
+
Examples
|
|
104
|
+
--------
|
|
105
|
+
>>> config = ClickHouseConfig.from_env()
|
|
106
|
+
>>> print(config.host, config.port)
|
|
107
|
+
localhost 8123
|
|
108
|
+
"""
|
|
109
|
+
return cls(
|
|
110
|
+
host=os.getenv("CLICKHOUSE_HOST", "localhost"),
|
|
111
|
+
port=int(os.getenv("CLICKHOUSE_PORT", "8123")),
|
|
112
|
+
database=os.getenv("RANGEBAR_CH_DATABASE", "rangebar_cache"),
|
|
113
|
+
mode=get_connection_mode(),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def validate(self) -> None:
|
|
117
|
+
"""Validate configuration.
|
|
118
|
+
|
|
119
|
+
Raises
|
|
120
|
+
------
|
|
121
|
+
ClickHouseConfigError
|
|
122
|
+
If configuration is invalid
|
|
123
|
+
"""
|
|
124
|
+
if self.port < 1 or self.port > 65535:
|
|
125
|
+
msg = f"Invalid port: {self.port}"
|
|
126
|
+
raise ClickHouseConfigError(msg)
|
|
127
|
+
|
|
128
|
+
if not self.database:
|
|
129
|
+
msg = "Database name cannot be empty"
|
|
130
|
+
raise ClickHouseConfigError(msg)
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def connection_string(self) -> str:
|
|
134
|
+
"""Get connection string for display/logging.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
str
|
|
139
|
+
Connection string (host:port/database)
|
|
140
|
+
"""
|
|
141
|
+
return f"{self.host}:{self.port}/{self.database}"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""ClickHouse client mixin for rangebar cache classes.
|
|
2
|
+
|
|
3
|
+
This module provides a mixin for managing ClickHouse client lifecycle,
|
|
4
|
+
following the pattern from exness-data-preprocess.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from .client import get_client
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
import clickhouse_connect
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ClickHouseClientMixin:
|
|
18
|
+
"""Mixin for classes that need ClickHouse client access.
|
|
19
|
+
|
|
20
|
+
Provides client lifecycle management with ownership tracking.
|
|
21
|
+
If a client is provided externally, the class doesn't own it.
|
|
22
|
+
If created internally, the class owns it and will close it.
|
|
23
|
+
|
|
24
|
+
Attributes
|
|
25
|
+
----------
|
|
26
|
+
_client : Client | None
|
|
27
|
+
ClickHouse client instance
|
|
28
|
+
_owns_client : bool
|
|
29
|
+
Whether this instance owns the client (should close it)
|
|
30
|
+
|
|
31
|
+
Examples
|
|
32
|
+
--------
|
|
33
|
+
>>> class MyCache(ClickHouseClientMixin):
|
|
34
|
+
... def __init__(self, client=None):
|
|
35
|
+
... self._init_client(client)
|
|
36
|
+
...
|
|
37
|
+
>>> cache = MyCache() # Creates own client
|
|
38
|
+
>>> cache.client.command("SELECT 1")
|
|
39
|
+
'1'
|
|
40
|
+
>>> cache.close() # Closes client
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
_client: clickhouse_connect.driver.Client | None = None
|
|
44
|
+
_owns_client: bool = False
|
|
45
|
+
|
|
46
|
+
def _init_client(
|
|
47
|
+
self,
|
|
48
|
+
client: clickhouse_connect.driver.Client | None = None,
|
|
49
|
+
host: str = "localhost",
|
|
50
|
+
port: int = 8123,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Initialize the client.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
client : Client | None
|
|
57
|
+
External client to use. If None, creates a new client.
|
|
58
|
+
host : str
|
|
59
|
+
Host for new client (default: localhost)
|
|
60
|
+
port : int
|
|
61
|
+
Port for new client (default: 8123)
|
|
62
|
+
"""
|
|
63
|
+
if client is not None:
|
|
64
|
+
self._client = client
|
|
65
|
+
self._owns_client = False
|
|
66
|
+
else:
|
|
67
|
+
self._client = None # Lazy initialization
|
|
68
|
+
self._owns_client = True
|
|
69
|
+
self._client_host = host
|
|
70
|
+
self._client_port = port
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def client(self) -> clickhouse_connect.driver.Client:
|
|
74
|
+
"""Get the ClickHouse client.
|
|
75
|
+
|
|
76
|
+
Creates a new client if one doesn't exist and this instance
|
|
77
|
+
owns its client.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
Client
|
|
82
|
+
ClickHouse client
|
|
83
|
+
|
|
84
|
+
Raises
|
|
85
|
+
------
|
|
86
|
+
ClickHouseUnavailableError
|
|
87
|
+
If client creation fails
|
|
88
|
+
"""
|
|
89
|
+
if self._client is None:
|
|
90
|
+
if self._owns_client:
|
|
91
|
+
self._client = get_client(
|
|
92
|
+
host=getattr(self, "_client_host", "localhost"),
|
|
93
|
+
port=getattr(self, "_client_port", 8123),
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
msg = "No client available and this instance doesn't own one"
|
|
97
|
+
raise RuntimeError(msg)
|
|
98
|
+
return self._client
|
|
99
|
+
|
|
100
|
+
def close(self) -> None:
|
|
101
|
+
"""Close the client if owned by this instance.
|
|
102
|
+
|
|
103
|
+
Safe to call multiple times. Only closes client if this
|
|
104
|
+
instance created it.
|
|
105
|
+
"""
|
|
106
|
+
if self._owns_client and self._client is not None:
|
|
107
|
+
try:
|
|
108
|
+
self._client.close()
|
|
109
|
+
except Exception:
|
|
110
|
+
pass # Ignore close errors
|
|
111
|
+
finally:
|
|
112
|
+
self._client = None
|
|
113
|
+
|
|
114
|
+
def __enter__(self) -> ClickHouseClientMixin:
|
|
115
|
+
"""Context manager entry."""
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
def __exit__(self, *args: object) -> None:
|
|
119
|
+
"""Context manager exit - closes client."""
|
|
120
|
+
self.close()
|