pineforge-data 0.2.0__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.
@@ -0,0 +1,226 @@
1
+ """Run backtests through the published pineforge-release container."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import shutil
8
+ import subprocess
9
+ import tempfile
10
+ from collections.abc import Mapping, Sequence
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+ from typing import Literal
14
+
15
+ from .backtest import BacktestOptions, JsonValue
16
+ from .models import Bar, Instrument
17
+ from .release_contract import (
18
+ DEFAULT_RELEASE_IMAGE,
19
+ ReleaseContractError,
20
+ parse_release_report,
21
+ release_environment,
22
+ release_response,
23
+ write_release_inputs,
24
+ )
25
+
26
+ PullPolicy = Literal["always", "missing", "never"]
27
+
28
+
29
+ class DockerPrerequisiteError(RuntimeError):
30
+ """Docker or the configured release image is unavailable."""
31
+
32
+
33
+ class DockerExecutionError(RuntimeError):
34
+ """The isolated release-container backtest failed."""
35
+
36
+
37
+ def _completed_error(completed: subprocess.CompletedProcess[str]) -> str:
38
+ return completed.stderr.strip() or completed.stdout.strip() or "command failed without output"
39
+
40
+
41
+ @dataclass(slots=True)
42
+ class DockerBacktestRuntime:
43
+ """Pull and execute an immutable pineforge-release image."""
44
+
45
+ image: str = DEFAULT_RELEASE_IMAGE
46
+ pull_policy: PullPolicy = "missing"
47
+ timeout_seconds: float = 300.0
48
+
49
+ def __post_init__(self) -> None:
50
+ if not self.image.strip():
51
+ raise ValueError("image must not be empty")
52
+ if self.pull_policy not in ("always", "missing", "never"):
53
+ raise ValueError(f"invalid pull policy: {self.pull_policy}")
54
+ if self.timeout_seconds <= 0:
55
+ raise ValueError("timeout_seconds must be positive")
56
+
57
+ def resolved_image(self) -> str:
58
+ return self.image
59
+
60
+ def _check_docker(self) -> None:
61
+ if shutil.which("docker") is None:
62
+ raise DockerPrerequisiteError(
63
+ "Docker is required but the `docker` command was not found"
64
+ )
65
+ completed = subprocess.run(
66
+ ["docker", "info", "--format", "{{.ServerVersion}}"],
67
+ text=True,
68
+ capture_output=True,
69
+ check=False,
70
+ )
71
+ if completed.returncode != 0:
72
+ raise DockerPrerequisiteError(
73
+ f"Docker daemon is unavailable: {_completed_error(completed)}"
74
+ )
75
+
76
+ def _image_exists(self) -> bool:
77
+ completed = subprocess.run(
78
+ ["docker", "image", "inspect", self.image],
79
+ text=True,
80
+ stdout=subprocess.DEVNULL,
81
+ stderr=subprocess.DEVNULL,
82
+ check=False,
83
+ )
84
+ return completed.returncode == 0
85
+
86
+ def _pull_image(self) -> None:
87
+ completed = subprocess.run(
88
+ ["docker", "pull", self.image],
89
+ text=True,
90
+ capture_output=True,
91
+ check=False,
92
+ )
93
+ if completed.returncode != 0:
94
+ raise DockerPrerequisiteError(
95
+ f"failed to pull pineforge-release image: {_completed_error(completed)}"
96
+ )
97
+
98
+ def _image_identity(self) -> dict[str, object]:
99
+ completed = subprocess.run(
100
+ ["docker", "image", "inspect", self.image],
101
+ text=True,
102
+ capture_output=True,
103
+ check=False,
104
+ )
105
+ if completed.returncode != 0:
106
+ return {}
107
+ try:
108
+ values = json.loads(completed.stdout)
109
+ value = values[0]
110
+ labels = value.get("Config", {}).get("Labels", {})
111
+ digests = value.get("RepoDigests", [])
112
+ except (IndexError, AttributeError, json.JSONDecodeError, TypeError):
113
+ return {}
114
+ if not isinstance(labels, dict) or not isinstance(digests, list):
115
+ return {}
116
+ return {
117
+ "resolved_digest": str(digests[0]) if digests else None,
118
+ "release_version": labels.get("org.opencontainers.image.version"),
119
+ "engine_version": labels.get("io.pineforge.engine.version"),
120
+ "codegen_version": labels.get("io.pineforge.codegen.version"),
121
+ }
122
+
123
+ def ensure_image(self) -> str:
124
+ """Apply the configured pull policy and return the immutable image ref."""
125
+
126
+ self._check_docker()
127
+ exists = self._image_exists()
128
+ if self.pull_policy == "always" or (self.pull_policy == "missing" and not exists):
129
+ self._pull_image()
130
+ exists = True
131
+ if not exists:
132
+ raise DockerPrerequisiteError(
133
+ f"pineforge-release image is not available locally: {self.image}"
134
+ )
135
+ return self.image
136
+
137
+ @staticmethod
138
+ def _remove_timed_out_container(cid_path: Path) -> None:
139
+ if not cid_path.is_file():
140
+ return
141
+ container_id = cid_path.read_text(encoding="utf-8").strip()
142
+ if container_id:
143
+ subprocess.run(
144
+ ["docker", "rm", "--force", container_id],
145
+ stdout=subprocess.DEVNULL,
146
+ stderr=subprocess.DEVNULL,
147
+ check=False,
148
+ )
149
+
150
+ def run(
151
+ self,
152
+ pine_source: str,
153
+ bars: Sequence[Bar],
154
+ *,
155
+ instrument: Instrument,
156
+ source: str,
157
+ options: BacktestOptions,
158
+ strategy_params: Mapping[str, JsonValue] | None = None,
159
+ strategy_overrides: Mapping[str, JsonValue] | None = None,
160
+ ) -> dict[str, object]:
161
+ """Run PineScript and normalized OHLCV in pineforge-release."""
162
+
163
+ image = self.ensure_image()
164
+ with tempfile.TemporaryDirectory(prefix="pineforge-data-") as temporary:
165
+ workspace = Path(temporary)
166
+ write_release_inputs(workspace, pine_source, bars, instrument)
167
+ environment = release_environment(
168
+ "/in",
169
+ instrument,
170
+ options,
171
+ strategy_params,
172
+ strategy_overrides,
173
+ )
174
+ environment["PINEFORGE_DATA_SOURCE"] = source
175
+ cid_path = workspace / "container.cid"
176
+ command = [
177
+ "docker",
178
+ "run",
179
+ "--rm",
180
+ "--cidfile",
181
+ str(cid_path),
182
+ "--network",
183
+ "none",
184
+ "--read-only",
185
+ "--tmpfs",
186
+ "/tmp:rw,exec,nosuid,nodev,size=512m",
187
+ "--cap-drop",
188
+ "ALL",
189
+ "--security-opt",
190
+ "no-new-privileges",
191
+ "--mount",
192
+ f"type=bind,src={workspace},dst=/in,readonly",
193
+ ]
194
+ for key, value in sorted(environment.items()):
195
+ command.extend(("--env", f"{key}={value}"))
196
+ command.append(image)
197
+ try:
198
+ completed = subprocess.run(
199
+ command,
200
+ text=True,
201
+ capture_output=True,
202
+ check=False,
203
+ timeout=self.timeout_seconds,
204
+ env=os.environ.copy(),
205
+ )
206
+ except subprocess.TimeoutExpired as exc:
207
+ self._remove_timed_out_container(cid_path)
208
+ raise DockerExecutionError(
209
+ f"pineforge-release exceeded {self.timeout_seconds:g} seconds"
210
+ ) from exc
211
+ if completed.returncode != 0:
212
+ phase = {2: "input", 3: "compile", 4: "backtest", 5: "transpile"}.get(
213
+ completed.returncode, "container"
214
+ )
215
+ raise DockerExecutionError(f"{phase} failed: {_completed_error(completed)}")
216
+ try:
217
+ report = parse_release_report(completed.stdout)
218
+ except ReleaseContractError as exc:
219
+ raise DockerExecutionError(str(exc)) from exc
220
+ return release_response(
221
+ report,
222
+ release_image=image,
223
+ mode="local-container",
224
+ request_id=None,
225
+ runtime_metadata=self._image_identity(),
226
+ )
@@ -0,0 +1,185 @@
1
+ """Dependency-free interoperability with PineForge's streaming C ABI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ctypes
6
+ from collections.abc import Sequence
7
+
8
+ from .errors import EngineStreamError
9
+ from .models import Bar, Instrument, TradeTick
10
+
11
+
12
+ class PfBar(ctypes.Structure):
13
+ """``ctypes`` mirror of PineForge's ``pf_bar_t``."""
14
+
15
+ _fields_ = [
16
+ ("open", ctypes.c_double),
17
+ ("high", ctypes.c_double),
18
+ ("low", ctypes.c_double),
19
+ ("close", ctypes.c_double),
20
+ ("volume", ctypes.c_double),
21
+ ("timestamp", ctypes.c_int64),
22
+ ]
23
+
24
+
25
+ class PfTradeTick(ctypes.Structure):
26
+ """``ctypes`` mirror of PineForge's ``pf_trade_tick_t``."""
27
+
28
+ _fields_ = [
29
+ ("timestamp", ctypes.c_int64),
30
+ ("sequence", ctypes.c_uint64),
31
+ ("price", ctypes.c_double),
32
+ ("quantity", ctypes.c_double),
33
+ ]
34
+
35
+
36
+ def _require_instrument(
37
+ records: Sequence[Bar] | Sequence[TradeTick], expected: Instrument | None
38
+ ) -> None:
39
+ if not records:
40
+ return
41
+ reference = expected or records[0].instrument
42
+ if any(record.instrument != reference for record in records):
43
+ raise ValueError("all records must belong to the expected instrument")
44
+
45
+
46
+ def pack_bars(bars: Sequence[Bar], *, instrument: Instrument | None = None) -> ctypes.Array[PfBar]:
47
+ """Pack normalized bars into one contiguous ``pf_bar_t`` array."""
48
+
49
+ _require_instrument(bars, instrument)
50
+ array_type = PfBar * len(bars)
51
+ return array_type(
52
+ *(
53
+ PfBar(bar.open, bar.high, bar.low, bar.close, bar.volume, bar.timestamp_ms)
54
+ for bar in bars
55
+ )
56
+ )
57
+
58
+
59
+ def pack_trade_ticks(
60
+ ticks: Sequence[TradeTick], *, instrument: Instrument | None = None
61
+ ) -> ctypes.Array[PfTradeTick]:
62
+ """Pack normalized trades into one contiguous ``pf_trade_tick_t`` array."""
63
+
64
+ _require_instrument(ticks, instrument)
65
+ array_type = PfTradeTick * len(ticks)
66
+ return array_type(
67
+ *(
68
+ PfTradeTick(tick.timestamp_ms, tick.sequence, tick.price, tick.quantity)
69
+ for tick in ticks
70
+ )
71
+ )
72
+
73
+
74
+ class EngineStreamSink:
75
+ """Send normalized records to an existing PineForge strategy instance.
76
+
77
+ The caller owns both ``library`` and ``state``. This adapter configures and
78
+ invokes only the streaming functions; it never creates or frees a strategy.
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ library: ctypes.CDLL,
84
+ state: int | ctypes.c_void_p,
85
+ instrument: Instrument,
86
+ ) -> None:
87
+ if not state:
88
+ raise ValueError("state must be a non-null PineForge strategy handle")
89
+ self._library = library
90
+ self._state = state
91
+ self.instrument = instrument
92
+ self._configure_signatures()
93
+
94
+ def _configure_signatures(self) -> None:
95
+ self._library.strategy_get_last_error.argtypes = [ctypes.c_void_p]
96
+ self._library.strategy_get_last_error.restype = ctypes.c_char_p
97
+ self._library.strategy_stream_begin.argtypes = [
98
+ ctypes.c_void_p,
99
+ ctypes.POINTER(PfBar),
100
+ ctypes.c_int,
101
+ ctypes.c_char_p,
102
+ ctypes.c_char_p,
103
+ ]
104
+ self._library.strategy_stream_begin.restype = ctypes.c_int
105
+ self._library.strategy_stream_push_tick.argtypes = [
106
+ ctypes.c_void_p,
107
+ ctypes.POINTER(PfTradeTick),
108
+ ]
109
+ self._library.strategy_stream_push_tick.restype = ctypes.c_int
110
+ self._library.strategy_stream_push_ticks.argtypes = [
111
+ ctypes.c_void_p,
112
+ ctypes.POINTER(PfTradeTick),
113
+ ctypes.c_int,
114
+ ]
115
+ self._library.strategy_stream_push_ticks.restype = ctypes.c_int
116
+ self._library.strategy_stream_advance_time.argtypes = [ctypes.c_void_p, ctypes.c_int64]
117
+ self._library.strategy_stream_advance_time.restype = ctypes.c_int
118
+ self._library.strategy_stream_end.argtypes = [ctypes.c_void_p, ctypes.c_int]
119
+ self._library.strategy_stream_end.restype = ctypes.c_int
120
+
121
+ def _check(self, status: int, operation: str) -> None:
122
+ if status == 0:
123
+ return
124
+ raw = self._library.strategy_get_last_error(self._state)
125
+ detail = raw.decode("utf-8", "replace") if raw else "unknown engine error"
126
+ raise EngineStreamError(f"{operation}: {detail}")
127
+
128
+ def begin(
129
+ self,
130
+ warmup_bars: Sequence[Bar],
131
+ *,
132
+ input_timeframe: str,
133
+ script_timeframe: str,
134
+ ) -> None:
135
+ """Warm the strategy on confirmed bars and start realtime streaming."""
136
+
137
+ if not warmup_bars:
138
+ raise ValueError("warmup_bars must not be empty")
139
+ if not input_timeframe or not script_timeframe:
140
+ raise ValueError("input_timeframe and script_timeframe must not be empty")
141
+ packed = pack_bars(warmup_bars, instrument=self.instrument)
142
+ status = self._library.strategy_stream_begin(
143
+ self._state,
144
+ packed,
145
+ len(packed),
146
+ input_timeframe.encode(),
147
+ script_timeframe.encode(),
148
+ )
149
+ self._check(status, "stream begin")
150
+
151
+ def push_tick(self, tick: TradeTick) -> None:
152
+ """Push one normalized executed trade."""
153
+
154
+ if tick.instrument != self.instrument:
155
+ raise ValueError("tick does not belong to the sink instrument")
156
+ packed = PfTradeTick(tick.timestamp_ms, tick.sequence, tick.price, tick.quantity)
157
+ status = self._library.strategy_stream_push_tick(self._state, ctypes.byref(packed))
158
+ self._check(status, "stream tick")
159
+
160
+ def push_ticks(self, ticks: Sequence[TradeTick]) -> None:
161
+ """Push normalized executed trades in one contiguous ABI call."""
162
+
163
+ if not ticks:
164
+ return
165
+ packed = pack_trade_ticks(ticks, instrument=self.instrument)
166
+ status = self._library.strategy_stream_push_ticks(self._state, packed, len(packed))
167
+ self._check(status, "stream ticks")
168
+
169
+ def advance_time(self, timestamp_ms: int) -> None:
170
+ """Advance the stream clock so elapsed input bars can close."""
171
+
172
+ if timestamp_ms < 0:
173
+ raise ValueError("timestamp_ms must be non-negative")
174
+ status = self._library.strategy_stream_advance_time(self._state, timestamp_ms)
175
+ self._check(status, "stream advance")
176
+
177
+ def end(self, *, finalize_partial_input_bar: bool = False) -> None:
178
+ """End the stream, optionally dispatching its partial input bar."""
179
+
180
+ status = self._library.strategy_stream_end(self._state, int(finalize_partial_input_bar))
181
+ self._check(status, "stream end")
182
+
183
+
184
+ if ctypes.sizeof(PfBar) != 48 or ctypes.sizeof(PfTradeTick) != 32:
185
+ raise RuntimeError("unsupported platform ABI layout for PineForge streaming records")
@@ -0,0 +1,5 @@
1
+ """Exceptions raised at PineForge integration boundaries."""
2
+
3
+
4
+ class EngineStreamError(RuntimeError):
5
+ """A PineForge streaming C ABI call failed."""
@@ -0,0 +1,199 @@
1
+ """Provider-neutral records shared by adapters and engine sinks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import StrEnum
7
+ from math import isfinite
8
+
9
+
10
+ def _non_empty(value: str, field: str) -> None:
11
+ if not value.strip():
12
+ raise ValueError(f"{field} must not be empty")
13
+
14
+
15
+ def _non_negative(value: int, field: str) -> None:
16
+ if value < 0:
17
+ raise ValueError(f"{field} must be non-negative")
18
+
19
+
20
+ def _unix_ms(value: int, field: str) -> None:
21
+ _non_negative(value, field)
22
+ if value > 2**63 - 1:
23
+ raise ValueError(f"{field} must fit a signed 64-bit integer")
24
+
25
+
26
+ def _optional_text(value: str, field: str) -> None:
27
+ if value and not value.strip():
28
+ raise ValueError(f"{field} must not contain only whitespace")
29
+
30
+
31
+ class AssetClass(StrEnum):
32
+ """Broad economic asset class, independent of execution venue."""
33
+
34
+ UNKNOWN = "unknown"
35
+ CRYPTO = "crypto"
36
+ EQUITY = "equity"
37
+ FOREX = "forex"
38
+ COMMODITY = "commodity"
39
+ INDEX = "index"
40
+ FUND = "fund"
41
+ BOND = "bond"
42
+
43
+
44
+ class MarketType(StrEnum):
45
+ """Trading-market structure for an instrument listing."""
46
+
47
+ UNKNOWN = "unknown"
48
+ SPOT = "spot"
49
+ CASH = "cash"
50
+ SWAP = "swap"
51
+ FUTURE = "future"
52
+ OPTION = "option"
53
+ CFD = "cfd"
54
+
55
+
56
+ class OptionType(StrEnum):
57
+ CALL = "call"
58
+ PUT = "put"
59
+
60
+
61
+ @dataclass(frozen=True, slots=True)
62
+ class ContractSpec:
63
+ """Normalized derivative terms supplied by a market catalog."""
64
+
65
+ contract_size: float | None = None
66
+ linear: bool | None = None
67
+ inverse: bool | None = None
68
+ expiry_ms: int | None = None
69
+ strike: float | None = None
70
+ option_type: OptionType | None = None
71
+
72
+ def __post_init__(self) -> None:
73
+ if self.contract_size is not None and (
74
+ not isfinite(self.contract_size) or self.contract_size <= 0
75
+ ):
76
+ raise ValueError("contract_size must be finite and positive")
77
+ if self.linear is True and self.inverse is True:
78
+ raise ValueError("a contract cannot be both linear and inverse")
79
+ if self.expiry_ms is not None:
80
+ _unix_ms(self.expiry_ms, "expiry_ms")
81
+ if self.strike is not None and (not isfinite(self.strike) or self.strike <= 0):
82
+ raise ValueError("strike must be finite and positive")
83
+
84
+
85
+ @dataclass(frozen=True, slots=True)
86
+ class Instrument:
87
+ """A normalized market instrument with provider identity kept explicit."""
88
+
89
+ symbol: str
90
+ venue: str = ""
91
+ timezone: str = "UTC"
92
+ session: str = "24x7"
93
+ volume_unit: str = "base"
94
+ asset_class: AssetClass = AssetClass.UNKNOWN
95
+ market_type: MarketType = MarketType.UNKNOWN
96
+ base: str = ""
97
+ quote: str = ""
98
+ settle: str = ""
99
+ provider_id: str = ""
100
+ contract: ContractSpec | None = None
101
+
102
+ def __post_init__(self) -> None:
103
+ _non_empty(self.symbol, "symbol")
104
+ _non_empty(self.timezone, "timezone")
105
+ _non_empty(self.session, "session")
106
+ _non_empty(self.volume_unit, "volume_unit")
107
+ _optional_text(self.venue, "venue")
108
+ _optional_text(self.base, "base")
109
+ _optional_text(self.quote, "quote")
110
+ _optional_text(self.settle, "settle")
111
+ _optional_text(self.provider_id, "provider_id")
112
+
113
+
114
+ @dataclass(frozen=True, slots=True)
115
+ class MarketListing:
116
+ """One instrument as listed by one provider-bound venue."""
117
+
118
+ instrument: Instrument
119
+ active: bool | None = None
120
+ margin_supported: bool | None = None
121
+
122
+
123
+ @dataclass(frozen=True, slots=True)
124
+ class Bar:
125
+ """One confirmed, provider-normalized OHLCV bar."""
126
+
127
+ instrument: Instrument
128
+ timestamp_ms: int
129
+ open: float
130
+ high: float
131
+ low: float
132
+ close: float
133
+ volume: float
134
+ source: str
135
+
136
+ def __post_init__(self) -> None:
137
+ _unix_ms(self.timestamp_ms, "timestamp_ms")
138
+ _non_empty(self.source, "source")
139
+ prices = (self.open, self.high, self.low, self.close)
140
+ if not all(isfinite(value) and value > 0 for value in prices):
141
+ raise ValueError("OHLC prices must be finite and positive")
142
+ if self.high < max(self.open, self.close, self.low):
143
+ raise ValueError("high must be greater than or equal to OHLC values")
144
+ if self.low > min(self.open, self.close, self.high):
145
+ raise ValueError("low must be less than or equal to OHLC values")
146
+ if not isfinite(self.volume) or self.volume < 0:
147
+ raise ValueError("volume must be finite and non-negative")
148
+
149
+
150
+ @dataclass(frozen=True, slots=True)
151
+ class TradeTick:
152
+ """One normalized executed trade ready for the PineForge stream ABI."""
153
+
154
+ instrument: Instrument
155
+ timestamp_ms: int
156
+ sequence: int
157
+ price: float
158
+ quantity: float
159
+ source: str
160
+
161
+ def __post_init__(self) -> None:
162
+ _unix_ms(self.timestamp_ms, "timestamp_ms")
163
+ _non_negative(self.sequence, "sequence")
164
+ if self.sequence > 2**64 - 1:
165
+ raise ValueError("sequence must fit an unsigned 64-bit integer")
166
+ _non_empty(self.source, "source")
167
+ if not isfinite(self.price) or self.price <= 0:
168
+ raise ValueError("price must be finite and positive")
169
+ if not isfinite(self.quantity) or self.quantity < 0:
170
+ raise ValueError("quantity must be finite and non-negative")
171
+
172
+
173
+ @dataclass(frozen=True, slots=True)
174
+ class MacroObservation:
175
+ """A vintage-aware macro value safe to align without revised-data lookahead."""
176
+
177
+ key: str
178
+ currency: str
179
+ period_end_ms: int
180
+ released_at_ms: int
181
+ vintage_at_ms: int
182
+ value: float
183
+ unit: str
184
+ source: str
185
+
186
+ def __post_init__(self) -> None:
187
+ _non_empty(self.key, "key")
188
+ _non_empty(self.currency, "currency")
189
+ _non_empty(self.unit, "unit")
190
+ _non_empty(self.source, "source")
191
+ _unix_ms(self.period_end_ms, "period_end_ms")
192
+ _unix_ms(self.released_at_ms, "released_at_ms")
193
+ _unix_ms(self.vintage_at_ms, "vintage_at_ms")
194
+ if self.released_at_ms < self.period_end_ms:
195
+ raise ValueError("released_at_ms must not precede period_end_ms")
196
+ if self.vintage_at_ms < self.released_at_ms:
197
+ raise ValueError("vintage_at_ms must not precede released_at_ms")
198
+ if not isfinite(self.value):
199
+ raise ValueError("value must be finite")
@@ -0,0 +1,69 @@
1
+ """Public provider protocols and registry."""
2
+
3
+ from .base import (
4
+ HistoricalBarProvider,
5
+ LiveTradeProvider,
6
+ MacroDataProvider,
7
+ MarketCatalogProvider,
8
+ MarketDataProvider,
9
+ MarketNotFoundError,
10
+ )
11
+ from .ccxt import (
12
+ CcxtCapabilityError,
13
+ CcxtDataError,
14
+ CcxtDependencyError,
15
+ CcxtError,
16
+ CcxtProvider,
17
+ )
18
+ from .local import CsvBarProvider, SqliteBarProvider
19
+ from .registry import (
20
+ ENTRY_POINT_GROUP,
21
+ ProviderFactory,
22
+ ProviderNotFoundError,
23
+ ProviderRegistry,
24
+ ProviderRegistryError,
25
+ create_provider,
26
+ default_registry,
27
+ )
28
+ from .sqlalchemy import SqlAlchemyBarProvider, SqlAlchemyDependencyError
29
+ from .tabular import (
30
+ BarColumnMapping,
31
+ SchemaMappingError,
32
+ SourceColumn,
33
+ TabularBarProvider,
34
+ TabularDataError,
35
+ TabularSchema,
36
+ TimestampUnit,
37
+ )
38
+
39
+ __all__ = [
40
+ "ENTRY_POINT_GROUP",
41
+ "BarColumnMapping",
42
+ "CcxtCapabilityError",
43
+ "CcxtDataError",
44
+ "CcxtDependencyError",
45
+ "CcxtError",
46
+ "CcxtProvider",
47
+ "CsvBarProvider",
48
+ "HistoricalBarProvider",
49
+ "LiveTradeProvider",
50
+ "MacroDataProvider",
51
+ "MarketCatalogProvider",
52
+ "MarketDataProvider",
53
+ "MarketNotFoundError",
54
+ "ProviderFactory",
55
+ "ProviderNotFoundError",
56
+ "ProviderRegistry",
57
+ "ProviderRegistryError",
58
+ "SchemaMappingError",
59
+ "SourceColumn",
60
+ "SqlAlchemyBarProvider",
61
+ "SqlAlchemyDependencyError",
62
+ "SqliteBarProvider",
63
+ "TabularBarProvider",
64
+ "TabularDataError",
65
+ "TabularSchema",
66
+ "TimestampUnit",
67
+ "create_provider",
68
+ "default_registry",
69
+ ]