142 lines
4.9 KiB
Python
142 lines
4.9 KiB
Python
# # ⚠ Warning
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
#
|
|
# [🥭 Mango Markets](https://markets/) support is available at:
|
|
# [Docs](https://docs.markets/)
|
|
# [Discord](https://discord.gg/67jySBhxrg)
|
|
# [Twitter](https://twitter.com/mangomarkets)
|
|
# [Github](https://github.com/blockworks-foundation)
|
|
# [Email](mailto:hello@blockworks.foundation)
|
|
|
|
|
|
import rx
|
|
import rx.operators
|
|
import typing
|
|
|
|
from decimal import Decimal
|
|
from solana.publickey import PublicKey
|
|
|
|
from ...cache import Cache
|
|
from ...context import Context
|
|
from ...datetimes import utc_now
|
|
from ...loadedmarket import LoadedMarket
|
|
from ...observables import observable_pipeline_error_reporter
|
|
from ...oracle import (
|
|
Oracle,
|
|
OracleProvider,
|
|
OracleSource,
|
|
Price,
|
|
SupportedOracleFeature,
|
|
)
|
|
from ...perpmarket import PerpMarket
|
|
from ...spotmarket import SpotMarket
|
|
|
|
|
|
# # 🥭 Stub
|
|
#
|
|
# This file contains code specific to oracles on the Mango Stub Oracle.
|
|
#
|
|
|
|
|
|
# # 🥭 StubOracleConfidence constant
|
|
#
|
|
# The stub oracle doesn't provide a confidence value.
|
|
#
|
|
|
|
StubOracleConfidence: Decimal = Decimal(0)
|
|
|
|
|
|
# # 🥭 StubOracle class
|
|
#
|
|
# Implements the `Oracle` abstract base class specialised to the Stub Oracle.
|
|
#
|
|
|
|
|
|
class StubOracle(Oracle):
|
|
def __init__(
|
|
self, market: LoadedMarket, index: int, cache_address: PublicKey
|
|
) -> None:
|
|
name = f"Stub Oracle for {market.fully_qualified_symbol}"
|
|
super().__init__(name, market)
|
|
self.index: int = index
|
|
self.cache_address: PublicKey = cache_address
|
|
features: SupportedOracleFeature = SupportedOracleFeature.MID_PRICE
|
|
self.source: OracleSource = OracleSource("Stub Oracle", name, features, market)
|
|
|
|
def fetch_price(self, context: Context) -> Price:
|
|
cache: Cache = Cache.load(context, self.cache_address)
|
|
raw_price = cache.price_cache[self.index]
|
|
if raw_price is None:
|
|
raise Exception(
|
|
f"Stub Oracle does not contain a price for market {self.symbol} at index {self.index}."
|
|
)
|
|
# Should convert raw_price to actual price.
|
|
# Discord on stub price from lagzda:
|
|
# https://discord.com/channels/791995070613159966/853370356244152360/871871877382033478
|
|
# "Hey, related to the conversation above since we should be reporting native quote per native
|
|
# base I did the incorrect change. Instead of adjusting RAY etc stub oracles prices from 2 to
|
|
# 2_000_000, I should've adjusted the Pyth oracles prices which soon will be deployed. That
|
|
# will give you the consistent results, but you'll need to adjust your code"
|
|
return Price(
|
|
self.source,
|
|
utc_now(),
|
|
self.market,
|
|
raw_price.price,
|
|
raw_price.price,
|
|
raw_price.price,
|
|
StubOracleConfidence,
|
|
)
|
|
|
|
def to_streaming_observable(
|
|
self, context: Context
|
|
) -> rx.core.typing.Observable[Price]:
|
|
prices = rx.interval(1).pipe(
|
|
rx.operators.observe_on(context.create_thread_pool_scheduler()),
|
|
rx.operators.start_with(-1),
|
|
rx.operators.map(lambda _: self.fetch_price(context)),
|
|
rx.operators.catch(observable_pipeline_error_reporter),
|
|
rx.operators.retry(),
|
|
)
|
|
return typing.cast(rx.core.typing.Observable[Price], prices)
|
|
|
|
|
|
# # 🥭 StubOracleProvider class
|
|
#
|
|
# Implements the `OracleProvider` abstract base class specialised to the Serum Network.
|
|
#
|
|
|
|
|
|
class StubOracleProvider(OracleProvider):
|
|
def __init__(self) -> None:
|
|
super().__init__("Stub Oracle Factory")
|
|
|
|
def oracle_for_market(
|
|
self, context: Context, market: LoadedMarket
|
|
) -> typing.Optional[Oracle]:
|
|
if SpotMarket.isa(market):
|
|
spot_market = SpotMarket.ensure(market)
|
|
spot_index: int = spot_market.group.slot_by_spot_market_address(
|
|
market.address
|
|
).index
|
|
return StubOracle(spot_market, spot_index, spot_market.group.cache)
|
|
elif PerpMarket.isa(market):
|
|
perp_market = PerpMarket.ensure(market)
|
|
perp_index: int = perp_market.group.slot_by_perp_market_address(
|
|
market.address
|
|
).index
|
|
return StubOracle(perp_market, perp_index, perp_market.group.cache)
|
|
|
|
return None
|
|
|
|
def all_available_symbols(self, context: Context) -> typing.Sequence[str]:
|
|
all_markets = context.market_lookup.all_markets()
|
|
symbols: typing.List[str] = []
|
|
for market in all_markets:
|
|
symbols += [market.symbol]
|
|
return symbols
|