5
5
from pythclient .pythaccounts import PythPriceStatus
6
6
from pythclient .solana import SolanaPublicKey
7
7
8
+ PUBLISHER_EXCLUSION_DISTANCE = 25
9
+
8
10
9
11
@dataclass
10
12
class PublisherState :
13
+ publisher_name : str
11
14
symbol : str
12
15
public_key : SolanaPublicKey
13
16
status : PythPriceStatus
17
+ aggregate_status : PythPriceStatus
14
18
slot : int
15
- slot_aggregate : int
19
+ aggregate_slot : int
20
+ latest_block_slot : int
16
21
price : float
17
22
price_aggregate : float
18
23
confidence_interval : float
@@ -33,11 +38,11 @@ def state(self) -> PublisherState:
33
38
def run (self ) -> bool :
34
39
...
35
40
36
- def error_message (self , publishers : Dict [ str , str ] ) -> str :
41
+ def error_message (self ) -> str :
37
42
...
38
43
39
44
40
- class PublisherAggregateCheck (PublisherCheck ):
45
+ class PublisherWithinAggregateConfidenceCheck (PublisherCheck ):
41
46
def __init__ (self , state : PublisherState , config : PublisherCheckConfig ):
42
47
self .__state = state
43
48
self .__max_interval_distance : int = int (config ["max_interval_distance" ])
@@ -46,10 +51,23 @@ def state(self) -> PublisherState:
46
51
return self .__state
47
52
48
53
def run (self ) -> bool :
54
+ # Skip if not trading
55
+ if self .__state .status != PythPriceStatus .TRADING :
56
+ return True
57
+
58
+ # Skip if aggregate is not trading
59
+ if self .__state .aggregate_status != PythPriceStatus .TRADING :
60
+ return True
61
+
49
62
# Skip if confidence interval is zero
50
63
if self .__state .confidence_interval == 0 :
51
64
return True
52
65
66
+ # Pass if publisher slot is far from aggregate slot
67
+ distance = abs (self .__state .slot - self .__state .aggregate_slot )
68
+ if distance > PUBLISHER_EXCLUSION_DISTANCE :
69
+ return True
70
+
53
71
diff = self .__state .price - self .__state .price_aggregate
54
72
intervals_away = abs (diff / self .__state .confidence_interval_aggregate )
55
73
@@ -60,11 +78,16 @@ def run(self) -> bool:
60
78
# Fail
61
79
return False
62
80
63
- def error_message (self , publishers ) -> str :
81
+ def error_message (self ) -> str :
82
+ diff = self .__state .price - self .__state .price_aggregate
83
+ intervals_away = abs (diff / self .__state .confidence_interval_aggregate )
84
+
64
85
return dedent (
65
86
f"""
66
- { publishers [self .__state .public_key .key ]} price is too far from aggregate.
87
+ { self .__state .publisher_name } price not within aggregate confidence.
88
+ It is { intervals_away } times away from confidence.
67
89
90
+ Symbol: { self .__state .symbol }
68
91
Publisher price: { self .__state .price } ± { self .__state .confidence_interval }
69
92
Aggregate price: { self .__state .price_aggregate } ± { self .__state .confidence_interval_aggregate }
70
93
"""
@@ -81,7 +104,12 @@ def state(self) -> PublisherState:
81
104
82
105
def run (self ) -> bool :
83
106
# Skip if not trading
84
- if not self .__state .status == PythPriceStatus .TRADING :
107
+ if self .__state .status != PythPriceStatus .TRADING :
108
+ return True
109
+
110
+ # Pass if publisher slot is far from aggregate slot
111
+ distance = abs (self .__state .slot - self .__state .aggregate_slot )
112
+ if distance > PUBLISHER_EXCLUSION_DISTANCE :
85
113
return True
86
114
87
115
# Pass if confidence interval is greater than min_confidence_interval
@@ -91,11 +119,12 @@ def run(self) -> bool:
91
119
# Fail
92
120
return False
93
121
94
- def error_message (self , publishers ) -> str :
122
+ def error_message (self ) -> str :
95
123
return dedent (
96
124
f"""
97
- { publishers [ self .__state .public_key . key ] } confidence interval is too tight.
125
+ { self .__state .publisher_name } confidence interval is too tight.
98
126
127
+ Symbol: { self .__state .symbol }
99
128
Price: { self .__state .price }
100
129
Confidence interval: { self .__state .confidence_interval }
101
130
"""
@@ -106,27 +135,34 @@ class PublisherOfflineCheck(PublisherCheck):
106
135
def __init__ (self , state : PublisherState , config : PublisherCheckConfig ):
107
136
self .__state = state
108
137
self .__max_slot_distance : int = int (config ["max_slot_distance" ])
138
+ self .__abandoned_slot_distance : int = int (config ["abandoned_slot_distance" ])
109
139
110
140
def state (self ) -> PublisherState :
111
141
return self .__state
112
142
113
143
def run (self ) -> bool :
114
- distance = abs ( self .__state .slot - self .__state .slot_aggregate )
144
+ distance = self .__state .latest_block_slot - self .__state .slot
115
145
116
146
# Pass if publisher slot is not too far from aggregate slot
117
- if distance < 25 :
147
+ if distance < self .__max_slot_distance :
148
+ return True
149
+
150
+ # Pass if publisher has been inactive for a long time
151
+ if distance > self .__abandoned_slot_distance :
118
152
return True
119
153
120
154
# Fail
121
- return True
155
+ return False
122
156
123
- def error_message (self , publishers ) -> str :
157
+ def error_message (self ) -> str :
158
+ distance = self .__state .latest_block_slot - self .__state .slot
124
159
return dedent (
125
160
f"""
126
- { publishers [ self .__state .public_key . key ] } hasn't published recently.
161
+ { self .__state .publisher_name } hasn't published recently for { distance } slots .
127
162
163
+ Symbol: { self .__state .symbol }
128
164
Publisher slot: { self .__state .slot }
129
- Aggregate slot: { self .__state .slot_aggregate }
165
+ Aggregate slot: { self .__state .aggregate_slot }
130
166
"""
131
167
).strip ()
132
168
@@ -141,47 +177,51 @@ def state(self) -> PublisherState:
141
177
return self .__state
142
178
143
179
def run (self ) -> bool :
144
- price_diff = abs (self .__state .price - self .__state .price_aggregate )
145
- slot_diff = abs (self .__state .slot - self .__state .slot_aggregate )
180
+ # Skip if aggregate status is not trading
181
+ if self .__state .aggregate_status != PythPriceStatus .TRADING :
182
+ return True
146
183
147
184
# Skip if not trading
148
185
if self .__state .status != PythPriceStatus .TRADING :
149
186
return True
150
187
151
188
# Skip if publisher is too far behind
189
+ slot_diff = abs (self .__state .slot - self .__state .aggregate_slot )
152
190
if slot_diff > self .__max_slot_distance :
153
191
return True
154
192
155
- # Skip if no aggregate
156
- if self .__state .price_aggregate == 0 :
193
+ # Skip if published price is zero
194
+ if self .__state .price == 0 :
157
195
return True
158
196
159
- distance = (price_diff / self .__state .price_aggregate ) * 100
197
+ price_diff = abs (self .__state .price - self .__state .price_aggregate )
198
+ deviation = (price_diff / self .__state .price_aggregate ) * 100
160
199
161
200
# Pass if deviation is less than max distance
162
- if distance <= self .__max_aggregate_distance :
201
+ if deviation <= self .__max_aggregate_distance :
163
202
return True
164
203
165
204
# Fail
166
205
return False
167
206
168
- def error_message (self , publishers ) -> str :
207
+ def error_message (self ) -> str :
169
208
price_diff = abs (self .__state .price - self .__state .price_aggregate )
170
- distance = (price_diff / self .__state .price_aggregate ) * 100
209
+ deviation = (price_diff / self .__state .price_aggregate ) * 100
171
210
172
211
return dedent (
173
212
f"""
174
- { publishers [ self .__state .public_key . key ] } price is too far from aggregate.
213
+ { self .__state .publisher_name } price is too far from aggregate price .
175
214
215
+ Symbol: { self .__state .symbol }
176
216
Publisher price: { self .__state .price }
177
217
Aggregate price: { self .__state .price_aggregate }
178
- Distance : { distance } %
218
+ Deviation : { deviation } %
179
219
"""
180
220
).strip ()
181
221
182
222
183
223
PUBLISHER_CHECKS = [
184
- PublisherAggregateCheck ,
224
+ PublisherWithinAggregateConfidenceCheck ,
185
225
PublisherConfidenceIntervalCheck ,
186
226
PublisherOfflineCheck ,
187
227
PublisherPriceCheck ,
0 commit comments