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,87 @@
1
+ """Provider-neutral request and subscription objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+ from .models import AssetClass, Instrument, MarketListing, MarketType
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class MarketQuery:
12
+ """Provider-neutral filters for catalog discovery."""
13
+
14
+ asset_class: AssetClass | None = None
15
+ market_types: frozenset[MarketType] = field(default_factory=frozenset)
16
+ base: str = ""
17
+ quote: str = ""
18
+ settle: str = ""
19
+ active: bool | None = None
20
+ margin_supported: bool | None = None
21
+ linear: bool | None = None
22
+ inverse: bool | None = None
23
+
24
+ def matches(self, listing: MarketListing) -> bool:
25
+ """Return whether a normalized listing satisfies every supplied filter."""
26
+
27
+ instrument = listing.instrument
28
+ contract = instrument.contract
29
+ return (
30
+ (self.asset_class is None or instrument.asset_class is self.asset_class)
31
+ and (not self.market_types or instrument.market_type in self.market_types)
32
+ and (not self.base or instrument.base.casefold() == self.base.casefold())
33
+ and (not self.quote or instrument.quote.casefold() == self.quote.casefold())
34
+ and (not self.settle or instrument.settle.casefold() == self.settle.casefold())
35
+ and (self.active is None or listing.active is self.active)
36
+ and (self.margin_supported is None or listing.margin_supported is self.margin_supported)
37
+ and (self.linear is None or (contract is not None and contract.linear is self.linear))
38
+ and (
39
+ self.inverse is None or (contract is not None and contract.inverse is self.inverse)
40
+ )
41
+ )
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class BarRequest:
46
+ instrument: Instrument
47
+ timeframe: str
48
+ start_ms: int
49
+ end_ms: int
50
+ limit: int | None = None
51
+
52
+ def __post_init__(self) -> None:
53
+ if not self.timeframe.strip():
54
+ raise ValueError("timeframe must not be empty")
55
+ if self.start_ms < 0 or self.end_ms <= self.start_ms:
56
+ raise ValueError("bar request requires 0 <= start_ms < end_ms")
57
+ if self.limit is not None and self.limit <= 0:
58
+ raise ValueError("limit must be positive")
59
+
60
+
61
+ @dataclass(frozen=True, slots=True)
62
+ class TradeSubscription:
63
+ """A live stream beginning at ``start_ms`` and after ``start_sequence``."""
64
+
65
+ instrument: Instrument
66
+ start_ms: int | None = None
67
+ start_sequence: int = 0
68
+
69
+ def __post_init__(self) -> None:
70
+ if self.start_ms is not None and self.start_ms < 0:
71
+ raise ValueError("start_ms must be non-negative")
72
+ if self.start_sequence < 0:
73
+ raise ValueError("start_sequence must be non-negative")
74
+
75
+
76
+ @dataclass(frozen=True, slots=True)
77
+ class MacroRequest:
78
+ key: str
79
+ currency: str
80
+ start_ms: int
81
+ end_ms: int
82
+
83
+ def __post_init__(self) -> None:
84
+ if not self.key.strip() or not self.currency.strip():
85
+ raise ValueError("key and currency must not be empty")
86
+ if self.start_ms < 0 or self.end_ms <= self.start_ms:
87
+ raise ValueError("macro request requires 0 <= start_ms < end_ms")