diff --git a/pyproject.toml b/pyproject.toml index 62b3683..01e6e5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ ignore_missing_imports = true [tool.poetry] name = "pyth-observer" -version = "0.1.9" +version = "0.1.10" description = "Alerts and stuff" authors = [] readme = "README.md" diff --git a/pyth_observer/check/publisher.py b/pyth_observer/check/publisher.py index fc15e0c..415676d 100644 --- a/pyth_observer/check/publisher.py +++ b/pyth_observer/check/publisher.py @@ -194,8 +194,7 @@ def run(self) -> bool: if self.__state.price == 0: return True - price_diff = abs(self.__state.price - self.__state.price_aggregate) - deviation = (price_diff / self.__state.price_aggregate) * 100 + deviation = (self.ci_adjusted_price_diff() / self.__state.price_aggregate) * 100 # Pass if deviation is less than max distance if deviation <= self.__max_aggregate_distance: @@ -205,8 +204,7 @@ def run(self) -> bool: return False def error_message(self) -> str: - price_diff = abs(self.__state.price - self.__state.price_aggregate) - deviation = (price_diff / self.__state.price_aggregate) * 100 + deviation = (self.ci_adjusted_price_diff() / self.__state.price_aggregate) * 100 return dedent( f""" @@ -219,6 +217,12 @@ def error_message(self) -> str: """ ).strip() + # Returns the distance between the aggregate price and the closest side of the publisher's confidence interval + # Returns 0 if the aggregate price is within the publisher's confidence interval. + def ci_adjusted_price_diff(self) -> float: + price_only_diff = abs(self.__state.price - self.__state.price_aggregate) + return max(price_only_diff - self.__state.confidence_interval, 0) + PUBLISHER_CHECKS = [ PublisherWithinAggregateConfidenceCheck, diff --git a/tests/test_checks_price_feed.py b/tests/test_checks_price_feed.py index a49ac32..c6107c5 100644 --- a/tests/test_checks_price_feed.py +++ b/tests/test_checks_price_feed.py @@ -4,7 +4,7 @@ from pyth_observer.check.price_feed import PriceFeedOfflineCheck, PriceFeedState -def test_price_feed_aggregate_check(): +def test_price_feed_offline_check(): state = PriceFeedState( symbol="Crypto.BTC/USD", asset_type="Crypto", diff --git a/tests/test_checks_publisher.py b/tests/test_checks_publisher.py new file mode 100644 index 0000000..82e989d --- /dev/null +++ b/tests/test_checks_publisher.py @@ -0,0 +1,46 @@ +from pythclient.pythaccounts import PythPriceStatus +from pythclient.solana import SolanaPublicKey + +from pyth_observer.check.publisher import PublisherPriceCheck, PublisherState + + +def make_state( + pub_slot: int, + pub_price: float, + pub_conf: float, + agg_slot: int, + agg_price: float, + agg_conf: float, +) -> PublisherState: + return PublisherState( + publisher_name="publisher", + symbol="Crypto.BTC/USD", + public_key=SolanaPublicKey("2hgu6Umyokvo8FfSDdMa9nDKhcdv9Q4VvGNhRCeSWeD3"), + status=PythPriceStatus.TRADING, + aggregate_status=PythPriceStatus.TRADING, + slot=pub_slot, + aggregate_slot=agg_slot, + latest_block_slot=agg_slot, + price=pub_price, + price_aggregate=agg_price, + confidence_interval=pub_conf, + confidence_interval_aggregate=agg_conf, + ) + + +def test_publisher_price_check(): + def check_is_ok( + state: PublisherState, max_aggregate_distance: int, max_slot_distance: int + ) -> bool: + return PublisherPriceCheck( + state, + { + "max_aggregate_distance": max_aggregate_distance, + "max_slot_distance": max_slot_distance, + }, + ).run() + + # check triggering threshold for price difference + state1 = make_state(1, 100.0, 2.0, 1, 110.0, 1.0) + assert check_is_ok(state1, 10, 25) + assert not check_is_ok(state1, 6, 25)