2021-07-23 06:18:26 -07:00
|
|
|
# # ⚠ 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://mango.markets/) support is available at:
|
|
|
|
# [Docs](https://docs.mango.markets/)
|
|
|
|
# [Discord](https://discord.gg/67jySBhxrg)
|
|
|
|
# [Twitter](https://twitter.com/mangomarkets)
|
|
|
|
# [Github](https://github.com/blockworks-foundation)
|
|
|
|
# [Email](mailto:hello@blockworks.foundation)
|
|
|
|
|
2021-08-27 12:37:23 -07:00
|
|
|
import typing
|
|
|
|
|
2022-02-22 04:06:20 -08:00
|
|
|
from datetime import datetime, timedelta
|
2021-07-23 06:18:26 -07:00
|
|
|
from decimal import Decimal
|
|
|
|
from solana.publickey import PublicKey
|
|
|
|
|
|
|
|
from .accountinfo import AccountInfo
|
|
|
|
from .addressableaccount import AddressableAccount
|
|
|
|
from .context import Context
|
2022-02-22 04:06:20 -08:00
|
|
|
from .datetimes import utc_now
|
2021-11-09 13:08:06 -08:00
|
|
|
from .group import GroupSlot, Group
|
2021-11-08 03:39:09 -08:00
|
|
|
from .instrumentvalue import InstrumentValue
|
2021-07-23 06:18:26 -07:00
|
|
|
from .layouts import layouts
|
|
|
|
from .metadata import Metadata
|
2022-03-01 04:22:50 -08:00
|
|
|
from .observables import Disposable
|
2022-02-24 11:40:05 -08:00
|
|
|
from .tokens import Instrument, Token
|
2021-11-13 11:23:11 -08:00
|
|
|
from .tokenbank import TokenBank
|
2021-07-23 06:18:26 -07:00
|
|
|
from .version import Version
|
2022-03-01 04:22:50 -08:00
|
|
|
from .websocketsubscription import (
|
|
|
|
WebSocketAccountSubscription,
|
|
|
|
WebSocketSubscriptionManager,
|
|
|
|
)
|
2021-07-23 06:18:26 -07:00
|
|
|
|
|
|
|
|
2021-07-29 04:38:34 -07:00
|
|
|
class LiquidityMiningInfo:
|
2022-02-09 11:31:50 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
version: Version,
|
|
|
|
rate: Decimal,
|
|
|
|
max_depth_bps: Decimal,
|
|
|
|
period_start: datetime,
|
|
|
|
target_period_length: timedelta,
|
|
|
|
mngo_left: InstrumentValue,
|
|
|
|
mngo_per_period: InstrumentValue,
|
|
|
|
) -> None:
|
2021-07-29 04:38:34 -07:00
|
|
|
self.version: Version = version
|
|
|
|
|
|
|
|
self.rate: Decimal = rate
|
|
|
|
self.max_depth_bps: Decimal = max_depth_bps
|
2021-09-23 04:30:59 -07:00
|
|
|
self.period_start: datetime = period_start
|
|
|
|
self.target_period_length: timedelta = target_period_length
|
2021-11-08 03:39:09 -08:00
|
|
|
self.mngo_left: InstrumentValue = mngo_left
|
|
|
|
self.mngo_per_period: InstrumentValue = mngo_per_period
|
2021-07-29 04:38:34 -07:00
|
|
|
|
|
|
|
@staticmethod
|
2022-02-09 11:31:50 -08:00
|
|
|
def from_layout(
|
|
|
|
layout: typing.Any, version: Version, mngo: Token
|
|
|
|
) -> "LiquidityMiningInfo":
|
2021-07-29 04:38:34 -07:00
|
|
|
rate: Decimal = layout.rate
|
|
|
|
max_depth_bps: Decimal = layout.max_depth_bps
|
2021-09-23 04:30:59 -07:00
|
|
|
period_start: datetime = layout.period_start
|
2022-02-09 11:31:50 -08:00
|
|
|
target_period_length: timedelta = timedelta(
|
|
|
|
seconds=float(layout.target_period_length)
|
|
|
|
)
|
|
|
|
mngo_left: InstrumentValue = InstrumentValue(
|
|
|
|
mngo, mngo.shift_to_decimals(layout.mngo_left)
|
|
|
|
)
|
|
|
|
mngo_per_period: InstrumentValue = InstrumentValue(
|
|
|
|
mngo, mngo.shift_to_decimals(layout.mngo_per_period)
|
|
|
|
)
|
2021-07-29 04:38:34 -07:00
|
|
|
|
2022-02-09 11:31:50 -08:00
|
|
|
return LiquidityMiningInfo(
|
|
|
|
version,
|
|
|
|
rate,
|
|
|
|
max_depth_bps,
|
|
|
|
period_start,
|
|
|
|
target_period_length,
|
|
|
|
mngo_left,
|
|
|
|
mngo_per_period,
|
|
|
|
)
|
2021-07-29 04:38:34 -07:00
|
|
|
|
2021-08-01 10:03:46 -07:00
|
|
|
def __str__(self) -> str:
|
2021-09-23 04:30:59 -07:00
|
|
|
# Some calculations here are basd on this message from 0xHiroku#0491 on Discord:
|
|
|
|
# https://discord.com/channels/791995070613159966/873184582948765736/889864341451599912
|
|
|
|
#
|
|
|
|
# // mngoLeft, mngoPerPeriod, periodStart, targetPeriodLength from PerpMarket.liquidityMiningInfo
|
|
|
|
#
|
|
|
|
# portion_given = 1 - mngoLeft / mngoPerPeriod
|
|
|
|
# elapsed = (<current_time> - periodStart) / targetPeriodLength
|
|
|
|
# est_next = elapsed / portion_given - elapsed
|
2022-02-22 04:06:20 -08:00
|
|
|
now: datetime = utc_now().replace(microsecond=0)
|
2021-11-08 03:39:09 -08:00
|
|
|
mngo_distributed: InstrumentValue = self.mngo_per_period - self.mngo_left
|
2021-10-28 03:54:29 -07:00
|
|
|
proportion_distributed: Decimal = Decimal(0)
|
2021-09-23 04:30:59 -07:00
|
|
|
elapsed: timedelta = now - self.period_start
|
|
|
|
elapsed_seconds: float = elapsed.total_seconds()
|
|
|
|
rounded_elapsed: timedelta = timedelta(seconds=int(elapsed_seconds))
|
2021-10-28 03:54:29 -07:00
|
|
|
estimated_duration_seconds: float = elapsed_seconds
|
2022-02-09 11:31:50 -08:00
|
|
|
estimated_duration: timedelta = timedelta(
|
|
|
|
seconds=int(estimated_duration_seconds)
|
|
|
|
)
|
|
|
|
estimated_remaining_seconds: float = (
|
|
|
|
estimated_duration_seconds - elapsed_seconds
|
|
|
|
)
|
|
|
|
estimated_remaining: timedelta = timedelta(
|
|
|
|
seconds=int(estimated_remaining_seconds)
|
|
|
|
)
|
2021-10-18 08:40:41 -07:00
|
|
|
estimated_end: datetime = now + estimated_remaining
|
2021-10-28 03:54:29 -07:00
|
|
|
if self.mngo_per_period.value != 0:
|
|
|
|
proportion_distributed = mngo_distributed.value / self.mngo_per_period.value
|
2022-02-09 11:31:50 -08:00
|
|
|
estimated_duration_seconds = elapsed_seconds / float(proportion_distributed)
|
2021-10-28 03:54:29 -07:00
|
|
|
estimated_duration = timedelta(seconds=int(estimated_duration_seconds))
|
2021-10-28 04:19:18 -07:00
|
|
|
estimated_remaining_seconds = estimated_duration_seconds - elapsed_seconds
|
2021-10-28 03:54:29 -07:00
|
|
|
estimated_remaining = timedelta(seconds=int(estimated_remaining_seconds))
|
|
|
|
estimated_end = now + estimated_remaining
|
2021-12-13 04:06:42 -08:00
|
|
|
return f"""« LiquidityMiningInfo {self.version}
|
2021-09-23 04:30:59 -07:00
|
|
|
Period Start : {self.period_start}
|
|
|
|
Period End (Est.): {estimated_end}
|
|
|
|
Target Duration : {self.target_period_length} hours
|
|
|
|
Elapsed : {rounded_elapsed} hours
|
|
|
|
Duration (Est.) : {estimated_duration} hours
|
|
|
|
Remaining (Est.) : {estimated_remaining} hours
|
|
|
|
Max Depth Bps : {self.max_depth_bps}
|
|
|
|
MNGO Per Period : {self.mngo_per_period}
|
|
|
|
MNGO Remaining : {self.mngo_left}
|
|
|
|
MNGO Distributed : {mngo_distributed}
|
|
|
|
% Distributed : {proportion_distributed:.2%}
|
|
|
|
Rate : {self.rate}
|
2021-07-29 04:38:34 -07:00
|
|
|
»"""
|
|
|
|
|
2021-09-23 04:30:59 -07:00
|
|
|
|
2021-07-23 06:18:26 -07:00
|
|
|
# # 🥭 PerpMarketDetails class
|
|
|
|
#
|
|
|
|
# `PerpMarketDetails` holds details of a particular perp market.
|
|
|
|
#
|
|
|
|
class PerpMarketDetails(AddressableAccount):
|
2022-02-09 11:31:50 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
account_info: AccountInfo,
|
|
|
|
version: Version,
|
|
|
|
meta_data: Metadata,
|
|
|
|
group: Group,
|
|
|
|
bids: PublicKey,
|
|
|
|
asks: PublicKey,
|
|
|
|
event_queue: PublicKey,
|
|
|
|
base_lot_size: Decimal,
|
|
|
|
quote_lot_size: Decimal,
|
|
|
|
long_funding: Decimal,
|
|
|
|
short_funding: Decimal,
|
|
|
|
open_interest: Decimal,
|
|
|
|
last_updated: datetime,
|
|
|
|
seq_num: Decimal,
|
|
|
|
fees_accrued: Decimal,
|
|
|
|
liquidity_mining_info: LiquidityMiningInfo,
|
|
|
|
mngo_vault: PublicKey,
|
|
|
|
) -> None:
|
2021-07-23 06:18:26 -07:00
|
|
|
super().__init__(account_info)
|
|
|
|
self.version: Version = version
|
|
|
|
|
|
|
|
self.meta_data: Metadata = meta_data
|
|
|
|
self.group: Group = group
|
|
|
|
self.bids: PublicKey = bids
|
|
|
|
self.asks: PublicKey = asks
|
|
|
|
self.event_queue: PublicKey = event_queue
|
|
|
|
self.base_lot_size: Decimal = base_lot_size
|
|
|
|
self.quote_lot_size: Decimal = quote_lot_size
|
|
|
|
self.long_funding: Decimal = long_funding
|
|
|
|
self.short_funding: Decimal = short_funding
|
|
|
|
self.open_interest: Decimal = open_interest
|
|
|
|
self.last_updated: datetime = last_updated
|
|
|
|
self.seq_num: Decimal = seq_num
|
|
|
|
self.fees_accrued: Decimal = fees_accrued
|
2021-07-29 04:38:34 -07:00
|
|
|
self.liquidity_mining_info: LiquidityMiningInfo = liquidity_mining_info
|
2021-07-23 06:18:26 -07:00
|
|
|
self.mngo_vault: PublicKey = mngo_vault
|
|
|
|
|
2021-11-09 13:08:06 -08:00
|
|
|
slot: GroupSlot = group.slot_by_perp_market_address(self.address)
|
2021-11-09 11:54:50 -08:00
|
|
|
if slot is None:
|
2022-02-09 11:31:50 -08:00
|
|
|
raise Exception(
|
|
|
|
f"Could not find slot for perp market {self.address} in group {group.address}."
|
|
|
|
)
|
2021-11-09 13:08:06 -08:00
|
|
|
|
|
|
|
self.market_index: int = slot.index
|
2021-07-23 06:18:26 -07:00
|
|
|
|
2021-11-09 11:54:50 -08:00
|
|
|
self.base_instrument: Instrument = slot.base_instrument
|
2021-11-13 11:23:11 -08:00
|
|
|
self.base_token: typing.Optional[TokenBank] = slot.base_token_bank
|
|
|
|
self.quote_token: TokenBank = group.shared_quote
|
2021-07-23 06:18:26 -07:00
|
|
|
|
|
|
|
@staticmethod
|
2022-02-09 11:31:50 -08:00
|
|
|
def from_layout(
|
|
|
|
layout: typing.Any, account_info: AccountInfo, version: Version, group: Group
|
|
|
|
) -> "PerpMarketDetails":
|
2021-07-23 06:18:26 -07:00
|
|
|
meta_data = Metadata.from_layout(layout.meta_data)
|
|
|
|
bids: PublicKey = layout.bids
|
|
|
|
asks: PublicKey = layout.asks
|
|
|
|
event_queue: PublicKey = layout.event_queue
|
|
|
|
base_lot_size: Decimal = layout.base_lot_size
|
|
|
|
quote_lot_size: Decimal = layout.quote_lot_size
|
|
|
|
|
|
|
|
long_funding: Decimal = layout.long_funding
|
|
|
|
short_funding: Decimal = layout.short_funding
|
|
|
|
open_interest: Decimal = layout.open_interest
|
|
|
|
last_updated: datetime = layout.last_updated
|
|
|
|
seq_num: Decimal = layout.seq_num
|
|
|
|
|
|
|
|
fees_accrued: Decimal = layout.fees_accrued
|
2021-07-29 04:38:34 -07:00
|
|
|
liquidity_mining_info: LiquidityMiningInfo = LiquidityMiningInfo.from_layout(
|
2022-02-09 11:31:50 -08:00
|
|
|
layout.liquidity_mining_info, Version.V1, group.liquidity_incentive_token
|
|
|
|
)
|
2021-07-23 06:18:26 -07:00
|
|
|
mngo_vault: PublicKey = layout.mngo_vault
|
|
|
|
|
2022-02-09 11:31:50 -08:00
|
|
|
return PerpMarketDetails(
|
|
|
|
account_info,
|
|
|
|
version,
|
|
|
|
meta_data,
|
|
|
|
group,
|
|
|
|
bids,
|
|
|
|
asks,
|
|
|
|
event_queue,
|
|
|
|
base_lot_size,
|
|
|
|
quote_lot_size,
|
|
|
|
long_funding,
|
|
|
|
short_funding,
|
|
|
|
open_interest,
|
|
|
|
last_updated,
|
|
|
|
seq_num,
|
|
|
|
fees_accrued,
|
|
|
|
liquidity_mining_info,
|
|
|
|
mngo_vault,
|
|
|
|
)
|
2021-07-23 06:18:26 -07:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def parse(account_info: AccountInfo, group: Group) -> "PerpMarketDetails":
|
|
|
|
data = account_info.data
|
|
|
|
if len(data) != layouts.PERP_MARKET.sizeof():
|
|
|
|
raise Exception(
|
2022-02-09 11:31:50 -08:00
|
|
|
f"PerpMarketDetails data length ({len(data)}) does not match expected size ({layouts.PERP_MARKET.sizeof()})"
|
|
|
|
)
|
2021-07-23 06:18:26 -07:00
|
|
|
|
|
|
|
layout = layouts.PERP_MARKET.parse(data)
|
|
|
|
return PerpMarketDetails.from_layout(layout, account_info, Version.V1, group)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def load(context: Context, address: PublicKey, group: Group) -> "PerpMarketDetails":
|
|
|
|
account_info = AccountInfo.load(context, address)
|
|
|
|
if account_info is None:
|
2022-02-09 11:31:50 -08:00
|
|
|
raise Exception(
|
|
|
|
f"PerpMarketDetails account not found at address '{address}'"
|
|
|
|
)
|
2021-07-23 06:18:26 -07:00
|
|
|
return PerpMarketDetails.parse(account_info, group)
|
|
|
|
|
2022-03-01 04:22:50 -08:00
|
|
|
def subscribe(
|
|
|
|
self,
|
|
|
|
context: Context,
|
|
|
|
websocketmanager: WebSocketSubscriptionManager,
|
|
|
|
callback: typing.Callable[["PerpMarketDetails"], None],
|
|
|
|
) -> Disposable:
|
|
|
|
def __parser(account_info: AccountInfo) -> PerpMarketDetails:
|
|
|
|
return PerpMarketDetails.parse(account_info, self.group)
|
|
|
|
|
|
|
|
subscription = WebSocketAccountSubscription(context, self.address, __parser)
|
|
|
|
websocketmanager.add(subscription)
|
|
|
|
subscription.publisher.subscribe(on_next=callback) # type: ignore[call-arg]
|
|
|
|
|
|
|
|
return subscription
|
|
|
|
|
2021-08-01 10:03:46 -07:00
|
|
|
def __str__(self) -> str:
|
2022-02-09 11:31:50 -08:00
|
|
|
liquidity_mining_info: str = f"{self.liquidity_mining_info}".replace(
|
|
|
|
"\n", "\n "
|
|
|
|
)
|
2021-12-13 04:06:42 -08:00
|
|
|
return f"""« PerpMarketDetails {self.version} [{self.address}]
|
2021-07-23 06:18:26 -07:00
|
|
|
{self.meta_data}
|
|
|
|
Group: {self.group.address}
|
|
|
|
Bids: {self.bids}
|
|
|
|
Asks: {self.asks}
|
|
|
|
Event Queue: {self.event_queue}
|
|
|
|
Long Funding: {self.long_funding}
|
|
|
|
Short Funding: {self.short_funding}
|
|
|
|
Open Interest: {self.open_interest}
|
|
|
|
Base Lot Size: {self.base_lot_size}
|
|
|
|
Quote Lot Size: {self.quote_lot_size}
|
|
|
|
Last Updated: {self.last_updated}
|
|
|
|
Seq Num: {self.seq_num}
|
|
|
|
Fees Accrued: {self.fees_accrued}
|
|
|
|
MNGO Vault: {self.mngo_vault}
|
2021-07-29 04:38:34 -07:00
|
|
|
{liquidity_mining_info}
|
2021-07-23 06:18:26 -07:00
|
|
|
»"""
|