Refactored MarketOperations and MarketInstructionBuilder.
This commit is contained in:
parent
01e121ad49
commit
e69e60b3ed
|
@ -14,8 +14,7 @@ from .combinableinstructions import CombinableInstructions
|
|||
from .constants import SYSTEM_PROGRAM_ADDRESS, SOL_MINT_ADDRESS, SOL_DECIMALS, SOL_DECIMAL_DIVISOR, WARNING_DISCLAIMER_TEXT, MangoConstants
|
||||
from .context import Context
|
||||
from .contextbuilder import ContextBuilder
|
||||
from .createmarketinstructionbuilder import create_market_instruction_builder
|
||||
from .createmarketoperations import create_market_operations
|
||||
from .createmarketoperations import create_market_instruction_builder, create_market_operations
|
||||
from .encoding import decode_binary, encode_binary, encode_key, encode_int
|
||||
from .ensuremarketloaded import ensure_market_loaded, load_market_by_symbol
|
||||
from .group import GroupSlotSpotMarket, GroupSlotPerpMarket, GroupSlot, Group
|
||||
|
@ -36,9 +35,8 @@ from .logmessages import expand_log_messages
|
|||
from .lotsizeconverter import LotSizeConverter, NullLotSizeConverter
|
||||
from .mangoinstruction import MangoInstruction
|
||||
from .market import InventorySource, Market, DryRunMarket
|
||||
from .marketinstructionbuilder import MarketInstructionBuilder, NullMarketInstructionBuilder
|
||||
from .marketlookup import MarketLookup, NullMarketLookup, CompoundMarketLookup
|
||||
from .marketoperations import MarketOperations, DryRunMarketOperations
|
||||
from .marketoperations import MarketInstructionBuilder, MarketOperations, NullMarketInstructionBuilder, NullMarketOperations
|
||||
from .metadata import Metadata
|
||||
from .modelstate import ModelState
|
||||
from .notification import NotificationTarget, TelegramNotificationTarget, DiscordNotificationTarget, MailjetNotificationTarget, CsvFileNotificationTarget, FilteringNotificationTarget, ConsoleNotificationTarget, CompoundNotificationTarget, parse_notification_target, NotificationHandler
|
||||
|
@ -54,8 +52,7 @@ from .perpaccount import PerpAccount
|
|||
from .perpeventqueue import PerpEvent, PerpFillEvent, PerpOutEvent, PerpUnknownEvent, PerpEventQueue, UnseenPerpEventChangesTracker
|
||||
from .perpmarket import PerpMarket, PerpMarketStub
|
||||
from .perpmarketdetails import PerpMarketDetails
|
||||
from .perpmarketinstructionbuilder import PerpMarketInstructionBuilder
|
||||
from .perpmarketoperations import PerpMarketOperations
|
||||
from .perpmarketoperations import PerpMarketInstructionBuilder, PerpMarketOperations
|
||||
from .perpopenorders import PerpOpenOrders
|
||||
from .placedorder import PlacedOrder, PlacedOrdersContainer
|
||||
from .publickey import encode_public_key_for_sorting
|
||||
|
@ -65,11 +62,9 @@ from .rootbank import NodeBank, RootBank
|
|||
from .serumeventqueue import SerumEventQueue, UnseenSerumEventChangesTracker
|
||||
from .serummarket import SerumMarket, SerumMarketStub
|
||||
from .serummarketlookup import SerumMarketLookup
|
||||
from .serummarketinstructionbuilder import SerumMarketInstructionBuilder
|
||||
from .serummarketoperations import SerumMarketOperations
|
||||
from .serummarketoperations import SerumMarketInstructionBuilder, SerumMarketOperations
|
||||
from .spotmarket import SpotMarket, SpotMarketStub
|
||||
from .spotmarketinstructionbuilder import SpotMarketInstructionBuilder
|
||||
from .spotmarketoperations import SpotMarketOperations
|
||||
from .spotmarketoperations import SpotMarketInstructionBuilder, SpotMarketOperations
|
||||
from .token import Instrument, Token, SolToken
|
||||
from .tokenaccount import TokenAccount
|
||||
from .tokeninfo import TokenInfo
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
# # ⚠ 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)
|
||||
|
||||
|
||||
from .account import Account
|
||||
from .context import Context
|
||||
from .ensuremarketloaded import ensure_market_loaded
|
||||
from .market import Market
|
||||
from .marketinstructionbuilder import MarketInstructionBuilder, NullMarketInstructionBuilder
|
||||
from .perpmarketinstructionbuilder import PerpMarketInstructionBuilder
|
||||
from .perpmarket import PerpMarket
|
||||
from .serummarket import SerumMarket
|
||||
from .serummarketinstructionbuilder import SerumMarketInstructionBuilder
|
||||
from .spotmarket import SpotMarket
|
||||
from .spotmarketinstructionbuilder import SpotMarketInstructionBuilder
|
||||
from .wallet import Wallet
|
||||
|
||||
# # 🥭 create_market_instruction_builder
|
||||
#
|
||||
# This function deals with the creation of a `MarketInstructionBuilder` object for a given `Market`.
|
||||
|
||||
|
||||
def create_market_instruction_builder(context: Context, wallet: Wallet, account: Account, market: Market, dry_run: bool = False) -> MarketInstructionBuilder:
|
||||
if dry_run:
|
||||
return NullMarketInstructionBuilder(market.symbol)
|
||||
|
||||
loaded_market: Market = ensure_market_loaded(context, market)
|
||||
if isinstance(loaded_market, SerumMarket):
|
||||
return SerumMarketInstructionBuilder.load(context, wallet, loaded_market)
|
||||
elif isinstance(loaded_market, SpotMarket):
|
||||
return SpotMarketInstructionBuilder.load(context, wallet, loaded_market.group, account, loaded_market)
|
||||
elif isinstance(loaded_market, PerpMarket):
|
||||
return PerpMarketInstructionBuilder.load(
|
||||
context, wallet, loaded_market.group, account, loaded_market)
|
||||
else:
|
||||
raise Exception(f"Could not find market instructions builder for market {market.symbol}")
|
|
@ -19,26 +19,43 @@ from .account import Account
|
|||
from .context import Context
|
||||
from .ensuremarketloaded import ensure_market_loaded
|
||||
from .market import Market
|
||||
from .marketoperations import MarketOperations, DryRunMarketOperations
|
||||
from .perpmarketinstructionbuilder import PerpMarketInstructionBuilder
|
||||
from .perpmarketoperations import PerpMarketOperations
|
||||
from .marketoperations import MarketInstructionBuilder, MarketOperations, NullMarketInstructionBuilder, NullMarketOperations
|
||||
from .perpmarketoperations import PerpMarketInstructionBuilder, PerpMarketOperations
|
||||
from .perpmarket import PerpMarket
|
||||
from .serummarket import SerumMarket
|
||||
from .serummarketinstructionbuilder import SerumMarketInstructionBuilder
|
||||
from .serummarketoperations import SerumMarketOperations
|
||||
from .serummarketoperations import SerumMarketInstructionBuilder, SerumMarketOperations
|
||||
from .spotmarket import SpotMarket
|
||||
from .spotmarketinstructionbuilder import SpotMarketInstructionBuilder
|
||||
from .spotmarketoperations import SpotMarketOperations
|
||||
from .spotmarketoperations import SpotMarketInstructionBuilder, SpotMarketOperations
|
||||
from .wallet import Wallet
|
||||
|
||||
|
||||
# # 🥭 create_market_instruction_builder
|
||||
#
|
||||
# This function deals with the creation of a `MarketInstructionBuilder` object for a given `Market`.
|
||||
#
|
||||
def create_market_instruction_builder(context: Context, wallet: Wallet, account: Account, market: Market, dry_run: bool = False) -> MarketInstructionBuilder:
|
||||
if dry_run:
|
||||
return NullMarketInstructionBuilder(market.symbol)
|
||||
|
||||
loaded_market: Market = ensure_market_loaded(context, market)
|
||||
if isinstance(loaded_market, SerumMarket):
|
||||
return SerumMarketInstructionBuilder.load(context, wallet, loaded_market)
|
||||
elif isinstance(loaded_market, SpotMarket):
|
||||
return SpotMarketInstructionBuilder.load(context, wallet, loaded_market.group, account, loaded_market)
|
||||
elif isinstance(loaded_market, PerpMarket):
|
||||
return PerpMarketInstructionBuilder.load(
|
||||
context, wallet, loaded_market.group, account, loaded_market)
|
||||
else:
|
||||
raise Exception(f"Could not find market instructions builder for market {market.symbol}")
|
||||
|
||||
|
||||
# # 🥭 create_market_operations
|
||||
#
|
||||
# This function deals with the creation of a `MarketOperations` object for a given `Market`.
|
||||
|
||||
|
||||
#
|
||||
def create_market_operations(context: Context, wallet: Wallet, account: typing.Optional[Account], market: Market, dry_run: bool = False) -> MarketOperations:
|
||||
if dry_run:
|
||||
return DryRunMarketOperations(market.symbol)
|
||||
return NullMarketOperations(market.symbol)
|
||||
|
||||
loaded_market: Market = ensure_market_loaded(context, market)
|
||||
if isinstance(loaded_market, SerumMarket):
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
# # ⚠ 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)
|
||||
|
||||
|
||||
import abc
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
from .combinableinstructions import CombinableInstructions
|
||||
from .orders import Order
|
||||
|
||||
|
||||
# # 🥭 MarketInstructionBuilder class
|
||||
#
|
||||
# This abstracts the process of buiding instructions for placing orders and cancelling orders.
|
||||
#
|
||||
# It's abstracted because we may want to have different implementations for different market types.
|
||||
#
|
||||
# Whichever choice is made, the calling code shouldn't have to care. It should be able to
|
||||
# use its `MarketInstructionBuilder` class as simply as:
|
||||
# ```
|
||||
# instruction_builder.build_cancel_order_instructions(order)
|
||||
# ```
|
||||
#
|
||||
# As a matter of policy for all InstructionBuidlers, construction and build_* methods should all work with
|
||||
# existing data, requiring no fetches from Solana or other sources. All necessary data should all be loaded
|
||||
# on initial setup in the `load()` method.
|
||||
#
|
||||
|
||||
class MarketInstructionBuilder(metaclass=abc.ABCMeta):
|
||||
def __init__(self):
|
||||
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_cancel_order_instructions(self, order: Order, ok_if_missing: bool = False) -> CombinableInstructions:
|
||||
raise NotImplementedError(
|
||||
"MarketInstructionBuilder.build_cancel_order_instructions() is not implemented on the base type.")
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_place_order_instructions(self, order: Order) -> CombinableInstructions:
|
||||
raise NotImplementedError(
|
||||
"MarketInstructionBuilder.build_place_order_instructions() is not implemented on the base type.")
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_settle_instructions(self) -> CombinableInstructions:
|
||||
raise NotImplementedError(
|
||||
"MarketInstructionBuilder.build_settle_instructions() is not implemented on the base type.")
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_crank_instructions(self, open_orders_addresses: typing.Sequence[PublicKey], limit: Decimal = Decimal(32)) -> CombinableInstructions:
|
||||
raise NotImplementedError(
|
||||
"MarketInstructionBuilder.build_crank_instructions() is not implemented on the base type.")
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_redeem_instructions(self) -> CombinableInstructions:
|
||||
raise NotImplementedError(
|
||||
"MarketInstructionBuilder.build_redeem_instructions() is not implemented on the base type.")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
||||
|
||||
|
||||
# # 🥭 NullMarketInstructionBuilder class
|
||||
#
|
||||
# A null, no-op, dry-run trade executor that can be plugged in anywhere a `MarketInstructionBuilder`
|
||||
# is expected, but which will not actually trade.
|
||||
#
|
||||
|
||||
class NullMarketInstructionBuilder(MarketInstructionBuilder):
|
||||
def __init__(self, symbol: str):
|
||||
super().__init__()
|
||||
self.symbol: str = symbol
|
||||
|
||||
def build_cancel_order_instructions(self, order: Order, ok_if_missing: bool = False) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_place_order_instructions(self, order: Order) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_settle_instructions(self) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_crank_instructions(self, addresses_to_crank: typing.Sequence[PublicKey], limit: Decimal = Decimal(32)) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_redeem_instructions(self) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« 𝙽𝚞𝚕𝚕𝙼𝚊𝚛𝚔𝚎𝚝𝙸𝚗𝚜𝚝𝚛𝚞𝚌𝚝𝚒𝚘𝚗𝙱𝚞𝚒𝚕𝚍𝚎𝚛 {self.symbol} »"
|
|
@ -21,6 +21,7 @@ import typing
|
|||
from decimal import Decimal
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
from .combinableinstructions import CombinableInstructions
|
||||
from .constants import SYSTEM_PROGRAM_ADDRESS
|
||||
from .market import Market, DryRunMarket
|
||||
from .orders import Order, OrderBook
|
||||
|
@ -37,6 +38,56 @@ from .orders import Order, OrderBook
|
|||
# order on, and the code in the `MarketOperations` be specialised for that market platform.
|
||||
#
|
||||
|
||||
|
||||
# # 🥭 MarketInstructionBuilder class
|
||||
#
|
||||
# This abstracts the process of buiding instructions for placing orders and cancelling orders.
|
||||
#
|
||||
# It's abstracted because we may want to have different implementations for different market types.
|
||||
#
|
||||
# Whichever choice is made, the calling code shouldn't have to care. It should be able to
|
||||
# use its `MarketInstructionBuilder` class as simply as:
|
||||
# ```
|
||||
# instruction_builder.build_cancel_order_instructions(order)
|
||||
# ```
|
||||
#
|
||||
# As a matter of policy for all InstructionBuidlers, construction and build_* methods should all work with
|
||||
# existing data, requiring no fetches from Solana or other sources. All necessary data should all be loaded
|
||||
# on initial setup in the `load()` method.
|
||||
#
|
||||
class MarketInstructionBuilder(metaclass=abc.ABCMeta):
|
||||
def __init__(self):
|
||||
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_cancel_order_instructions(self, order: Order, ok_if_missing: bool = False) -> CombinableInstructions:
|
||||
raise NotImplementedError(
|
||||
"MarketInstructionBuilder.build_cancel_order_instructions() is not implemented on the base type.")
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_place_order_instructions(self, order: Order) -> CombinableInstructions:
|
||||
raise NotImplementedError(
|
||||
"MarketInstructionBuilder.build_place_order_instructions() is not implemented on the base type.")
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_settle_instructions(self) -> CombinableInstructions:
|
||||
raise NotImplementedError(
|
||||
"MarketInstructionBuilder.build_settle_instructions() is not implemented on the base type.")
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_crank_instructions(self, open_orders_addresses: typing.Sequence[PublicKey], limit: Decimal = Decimal(32)) -> CombinableInstructions:
|
||||
raise NotImplementedError(
|
||||
"MarketInstructionBuilder.build_crank_instructions() is not implemented on the base type.")
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_redeem_instructions(self) -> CombinableInstructions:
|
||||
raise NotImplementedError(
|
||||
"MarketInstructionBuilder.build_redeem_instructions() is not implemented on the base type.")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
||||
|
||||
|
||||
# # 🥭 MarketOperations class
|
||||
#
|
||||
# This abstracts the process of placing orders, providing a base class for specialised operations.
|
||||
|
@ -92,12 +143,41 @@ class MarketOperations(metaclass=abc.ABCMeta):
|
|||
return f"{self}"
|
||||
|
||||
|
||||
# # 🥭 DryRunMarketOperations class
|
||||
# # 🥭 NullMarketInstructionBuilder class
|
||||
#
|
||||
# A null, no-op, dry-run trade executor that can be plugged in anywhere a `MarketInstructionBuilder`
|
||||
# is expected, but which will not actually trade.
|
||||
#
|
||||
class NullMarketInstructionBuilder(MarketInstructionBuilder):
|
||||
def __init__(self, symbol: str):
|
||||
super().__init__()
|
||||
self.symbol: str = symbol
|
||||
|
||||
def build_cancel_order_instructions(self, order: Order, ok_if_missing: bool = False) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_place_order_instructions(self, order: Order) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_settle_instructions(self) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_crank_instructions(self, addresses_to_crank: typing.Sequence[PublicKey], limit: Decimal = Decimal(32)) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_redeem_instructions(self) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« 𝙽𝚞𝚕𝚕𝙼𝚊𝚛𝚔𝚎𝚝𝙸𝚗𝚜𝚝𝚛𝚞𝚌𝚝𝚒𝚘𝚗𝙱𝚞𝚒𝚕𝚍𝚎𝚛 {self.symbol} »"
|
||||
|
||||
|
||||
# # 🥭 NullMarketOperations class
|
||||
#
|
||||
# A null, no-op, dry-run trade executor that can be plugged in anywhere a `MarketOperations`
|
||||
# is expected, but which will not actually trade.
|
||||
#
|
||||
class DryRunMarketOperations(MarketOperations):
|
||||
class NullMarketOperations(MarketOperations):
|
||||
def __init__(self, market_name: str):
|
||||
super().__init__(DryRunMarket(market_name))
|
||||
self.market_name: str = market_name
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
# # ⚠ 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)
|
||||
|
||||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
from .account import Account
|
||||
from .combinableinstructions import CombinableInstructions
|
||||
from .context import Context
|
||||
from .group import Group
|
||||
from .marketinstructionbuilder import MarketInstructionBuilder
|
||||
from .instructions import build_cancel_perp_order_instructions, build_mango_consume_events_instructions, build_place_perp_order_instructions, build_redeem_accrued_mango_instructions
|
||||
from .orders import Order
|
||||
from .perpmarket import PerpMarket
|
||||
from .tokeninfo import TokenInfo
|
||||
from .wallet import Wallet
|
||||
|
||||
|
||||
# # 🥭 PerpMarketInstructionBuilder
|
||||
#
|
||||
# This file deals with building instructions for Perp markets.
|
||||
#
|
||||
# As a matter of policy for all InstructionBuidlers, construction and build_* methods should all work with
|
||||
# existing data, requiring no fetches from Solana or other sources. All necessary data should all be loaded
|
||||
# on initial setup in the `load()` method.
|
||||
#
|
||||
|
||||
class PerpMarketInstructionBuilder(MarketInstructionBuilder):
|
||||
def __init__(self, context: Context, wallet: Wallet, group: Group, account: Account, perp_market: PerpMarket):
|
||||
super().__init__()
|
||||
self.context: Context = context
|
||||
self.wallet: Wallet = wallet
|
||||
self.group: Group = group
|
||||
self.account: Account = account
|
||||
self.perp_market: PerpMarket = perp_market
|
||||
self.mngo_token_info: TokenInfo = self.group.liquidity_incentive_token_info
|
||||
|
||||
@staticmethod
|
||||
def load(context: Context, wallet: Wallet, group: Group, account: Account, perp_market: PerpMarket) -> "PerpMarketInstructionBuilder":
|
||||
return PerpMarketInstructionBuilder(context, wallet, group, account, perp_market)
|
||||
|
||||
def build_cancel_order_instructions(self, order: Order, ok_if_missing: bool = False) -> CombinableInstructions:
|
||||
if self.perp_market.underlying_perp_market is None:
|
||||
raise Exception(f"PerpMarket {self.perp_market.symbol} has not been loaded.")
|
||||
return build_cancel_perp_order_instructions(
|
||||
self.context, self.wallet, self.account, self.perp_market.underlying_perp_market, order, ok_if_missing)
|
||||
|
||||
def build_place_order_instructions(self, order: Order) -> CombinableInstructions:
|
||||
if self.perp_market.underlying_perp_market is None:
|
||||
raise Exception(f"PerpMarket {self.perp_market.symbol} has not been loaded.")
|
||||
return build_place_perp_order_instructions(
|
||||
self.context, self.wallet, self.perp_market.underlying_perp_market.group, self.account, self.perp_market.underlying_perp_market, order.price, order.quantity, order.client_id, order.side, order.order_type)
|
||||
|
||||
def build_settle_instructions(self) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_crank_instructions(self, account_addresses: typing.Sequence[PublicKey], limit: Decimal = Decimal(32)) -> CombinableInstructions:
|
||||
if self.perp_market.underlying_perp_market is None:
|
||||
raise Exception(f"PerpMarket {self.perp_market.symbol} has not been loaded.")
|
||||
return build_mango_consume_events_instructions(self.context, self.group, self.perp_market.underlying_perp_market, account_addresses, limit)
|
||||
|
||||
def build_redeem_instructions(self) -> CombinableInstructions:
|
||||
return build_redeem_accrued_mango_instructions(self.context, self.wallet, self.perp_market, self.group, self.account, self.mngo_token_info)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return """« 𝙿𝚎𝚛𝚙𝙼𝚊𝚛𝚔𝚎𝚝𝙸𝚗𝚜𝚝𝚛𝚞𝚌𝚝𝚒𝚘𝚗𝚜 »"""
|
|
@ -23,13 +23,64 @@ from .account import Account
|
|||
from .combinableinstructions import CombinableInstructions
|
||||
from .constants import SYSTEM_PROGRAM_ADDRESS
|
||||
from .context import Context
|
||||
from .marketoperations import MarketOperations
|
||||
from .group import Group
|
||||
from .instructions import build_cancel_perp_order_instructions, build_mango_consume_events_instructions, build_place_perp_order_instructions, build_redeem_accrued_mango_instructions
|
||||
from .marketoperations import MarketInstructionBuilder, MarketOperations
|
||||
from .orders import Order, OrderBook
|
||||
from .perpmarketinstructionbuilder import PerpMarketInstructionBuilder
|
||||
from .perpmarket import PerpMarket
|
||||
from .tokeninfo import TokenInfo
|
||||
from .wallet import Wallet
|
||||
|
||||
|
||||
# # 🥭 PerpMarketInstructionBuilder
|
||||
#
|
||||
# This file deals with building instructions for Perp markets.
|
||||
#
|
||||
# As a matter of policy for all InstructionBuidlers, construction and build_* methods should all work with
|
||||
# existing data, requiring no fetches from Solana or other sources. All necessary data should all be loaded
|
||||
# on initial setup in the `load()` method.
|
||||
#
|
||||
class PerpMarketInstructionBuilder(MarketInstructionBuilder):
|
||||
def __init__(self, context: Context, wallet: Wallet, group: Group, account: Account, perp_market: PerpMarket):
|
||||
super().__init__()
|
||||
self.context: Context = context
|
||||
self.wallet: Wallet = wallet
|
||||
self.group: Group = group
|
||||
self.account: Account = account
|
||||
self.perp_market: PerpMarket = perp_market
|
||||
self.mngo_token_info: TokenInfo = self.group.liquidity_incentive_token_info
|
||||
|
||||
@staticmethod
|
||||
def load(context: Context, wallet: Wallet, group: Group, account: Account, perp_market: PerpMarket) -> "PerpMarketInstructionBuilder":
|
||||
return PerpMarketInstructionBuilder(context, wallet, group, account, perp_market)
|
||||
|
||||
def build_cancel_order_instructions(self, order: Order, ok_if_missing: bool = False) -> CombinableInstructions:
|
||||
if self.perp_market.underlying_perp_market is None:
|
||||
raise Exception(f"PerpMarket {self.perp_market.symbol} has not been loaded.")
|
||||
return build_cancel_perp_order_instructions(
|
||||
self.context, self.wallet, self.account, self.perp_market.underlying_perp_market, order, ok_if_missing)
|
||||
|
||||
def build_place_order_instructions(self, order: Order) -> CombinableInstructions:
|
||||
if self.perp_market.underlying_perp_market is None:
|
||||
raise Exception(f"PerpMarket {self.perp_market.symbol} has not been loaded.")
|
||||
return build_place_perp_order_instructions(
|
||||
self.context, self.wallet, self.perp_market.underlying_perp_market.group, self.account, self.perp_market.underlying_perp_market, order.price, order.quantity, order.client_id, order.side, order.order_type)
|
||||
|
||||
def build_settle_instructions(self) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_crank_instructions(self, account_addresses: typing.Sequence[PublicKey], limit: Decimal = Decimal(32)) -> CombinableInstructions:
|
||||
if self.perp_market.underlying_perp_market is None:
|
||||
raise Exception(f"PerpMarket {self.perp_market.symbol} has not been loaded.")
|
||||
return build_mango_consume_events_instructions(self.context, self.group, self.perp_market.underlying_perp_market, account_addresses, limit)
|
||||
|
||||
def build_redeem_instructions(self) -> CombinableInstructions:
|
||||
return build_redeem_accrued_mango_instructions(self.context, self.wallet, self.perp_market, self.group, self.account, self.mngo_token_info)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return """« 𝙿𝚎𝚛𝚙𝙼𝚊𝚛𝚔𝚎𝚝𝙸𝚗𝚜𝚝𝚛𝚞𝚌𝚝𝚒𝚘𝚗𝚜 »"""
|
||||
|
||||
|
||||
# # 🥭 PerpMarketOperations
|
||||
#
|
||||
# This file deals with placing orders for Perps.
|
||||
|
@ -94,4 +145,4 @@ class PerpMarketOperations(MarketOperations):
|
|||
return list([o for o in [*orderbook.bids, *orderbook.asks] if o.owner == self.account.address])
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« 𝙿𝚎𝚛𝚙𝚜𝙾𝚛𝚍𝚎𝚛𝙿𝚕𝚊𝚌𝚎𝚛 [{self.market_name}] »"""
|
||||
return f"""« 𝙿𝚎𝚛𝚙𝙼𝚊𝚛𝚔𝚎𝚝𝙾𝚙𝚎𝚛𝚊𝚝𝚒𝚘𝚗𝚜 [{self.market_name}] »"""
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
# # ⚠ 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)
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
from pyserum.market import Market as PySerumMarket
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
from .combinableinstructions import CombinableInstructions
|
||||
from .context import Context
|
||||
from .instructions import build_create_serum_open_orders_instructions, build_serum_consume_events_instructions, build_serum_settle_instructions, build_serum_place_order_instructions
|
||||
from .marketinstructionbuilder import MarketInstructionBuilder
|
||||
from .openorders import OpenOrders
|
||||
from .orders import Order, Side
|
||||
from .publickey import encode_public_key_for_sorting
|
||||
from .serummarket import SerumMarket
|
||||
from .token import Instrument, Token
|
||||
from .tokenaccount import TokenAccount
|
||||
from .wallet import Wallet
|
||||
|
||||
|
||||
# # 🥭 SerumMarketInstructionBuilder
|
||||
#
|
||||
# This file deals with building instructions for Serum markets.
|
||||
#
|
||||
# As a matter of policy for all InstructionBuidlers, construction and build_* methods should all work with
|
||||
# existing data, requiring no fetches from Solana or other sources. All necessary data should all be loaded
|
||||
# on initial setup in the `load()` method.
|
||||
#
|
||||
class SerumMarketInstructionBuilder(MarketInstructionBuilder):
|
||||
def __init__(self, context: Context, wallet: Wallet, serum_market: SerumMarket, raw_market: PySerumMarket, base_token_account: TokenAccount, quote_token_account: TokenAccount, open_orders_address: typing.Optional[PublicKey], fee_discount_token_address: typing.Optional[PublicKey]):
|
||||
super().__init__()
|
||||
self.context: Context = context
|
||||
self.wallet: Wallet = wallet
|
||||
self.serum_market: SerumMarket = serum_market
|
||||
self.raw_market: PySerumMarket = raw_market
|
||||
self.base_token_account: TokenAccount = base_token_account
|
||||
self.quote_token_account: TokenAccount = quote_token_account
|
||||
self.open_orders_address: typing.Optional[PublicKey] = open_orders_address
|
||||
self.fee_discount_token_address: typing.Optional[PublicKey] = fee_discount_token_address
|
||||
|
||||
@staticmethod
|
||||
def load(context: Context, wallet: Wallet, serum_market: SerumMarket) -> "SerumMarketInstructionBuilder":
|
||||
raw_market: PySerumMarket = PySerumMarket.load(
|
||||
context.client.compatible_client, serum_market.address, context.serum_program_address)
|
||||
|
||||
fee_discount_token_address: typing.Optional[PublicKey] = None
|
||||
srm_instrument: typing.Optional[Instrument] = context.instrument_lookup.find_by_symbol("SRM")
|
||||
if srm_instrument is not None:
|
||||
srm_token: Token = Token.ensure(srm_instrument)
|
||||
fee_discount_token_account = TokenAccount.fetch_largest_for_owner_and_token(
|
||||
context, wallet.address, srm_token)
|
||||
if fee_discount_token_account is not None:
|
||||
fee_discount_token_address = fee_discount_token_account.address
|
||||
|
||||
open_orders_address: typing.Optional[PublicKey] = None
|
||||
all_open_orders = OpenOrders.load_for_market_and_owner(
|
||||
context, serum_market.address, wallet.address, context.serum_program_address, serum_market.base.decimals, serum_market.quote.decimals)
|
||||
if len(all_open_orders) > 0:
|
||||
open_orders_address = all_open_orders[0].address
|
||||
|
||||
base_token_account = TokenAccount.fetch_largest_for_owner_and_token(context, wallet.address, serum_market.base)
|
||||
if base_token_account is None:
|
||||
raise Exception(f"Could not find source token account for base token {serum_market.base.symbol}.")
|
||||
|
||||
quote_token_account = TokenAccount.fetch_largest_for_owner_and_token(
|
||||
context, wallet.address, serum_market.quote)
|
||||
if quote_token_account is None:
|
||||
raise Exception(f"Could not find source token account for quote token {serum_market.quote.symbol}.")
|
||||
|
||||
return SerumMarketInstructionBuilder(context, wallet, serum_market, raw_market, base_token_account, quote_token_account, open_orders_address, fee_discount_token_address)
|
||||
|
||||
def build_cancel_order_instructions(self, order: Order, ok_if_missing: bool = False) -> CombinableInstructions:
|
||||
# For us to cancel an order, an open_orders account must already exist (or have existed).
|
||||
if self.open_orders_address is None:
|
||||
raise Exception(f"Cannot cancel order with client ID {order.client_id} - no OpenOrders account.")
|
||||
|
||||
raw_instruction = self.raw_market.make_cancel_order_by_client_id_instruction(
|
||||
self.wallet.keypair, self.open_orders_address, order.client_id
|
||||
)
|
||||
return CombinableInstructions.from_instruction(raw_instruction)
|
||||
|
||||
def build_place_order_instructions(self, order: Order) -> CombinableInstructions:
|
||||
ensure_open_orders = CombinableInstructions.empty()
|
||||
if self.open_orders_address is None:
|
||||
ensure_open_orders = self.build_create_openorders_instructions()
|
||||
|
||||
if self.open_orders_address is None:
|
||||
raise Exception("Failed to find or create OpenOrders address")
|
||||
|
||||
payer_token_account = self.quote_token_account if order.side == Side.BUY else self.base_token_account
|
||||
place = build_serum_place_order_instructions(self.context, self.wallet, self.raw_market,
|
||||
payer_token_account.address, self.open_orders_address,
|
||||
order.order_type, order.side, order.price,
|
||||
order.quantity, order.client_id,
|
||||
self.fee_discount_token_address)
|
||||
|
||||
return ensure_open_orders + place
|
||||
|
||||
def build_settle_instructions(self) -> CombinableInstructions:
|
||||
if self.open_orders_address is None:
|
||||
return CombinableInstructions.empty()
|
||||
return build_serum_settle_instructions(self.context, self.wallet, self.raw_market, self.open_orders_address, self.base_token_account.address, self.quote_token_account.address)
|
||||
|
||||
def build_crank_instructions(self, open_orders_addresses: typing.Sequence[PublicKey], limit: Decimal = Decimal(32)) -> CombinableInstructions:
|
||||
if self.open_orders_address is None:
|
||||
self.logger.debug("Returning empty crank instructions - no serum OpenOrders address provided.")
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
open_orders_to_crank: typing.Sequence[PublicKey] = [*open_orders_addresses, self.open_orders_address]
|
||||
distinct_open_orders_addresses: typing.List[PublicKey] = []
|
||||
for oo in open_orders_to_crank:
|
||||
if oo not in distinct_open_orders_addresses:
|
||||
distinct_open_orders_addresses += [oo]
|
||||
|
||||
limited_open_orders_addresses = distinct_open_orders_addresses[0:min(
|
||||
int(limit), len(distinct_open_orders_addresses))]
|
||||
|
||||
limited_open_orders_addresses.sort(key=encode_public_key_for_sorting)
|
||||
|
||||
return build_serum_consume_events_instructions(self.context, self.serum_market.address, self.raw_market.state.event_queue(), limited_open_orders_addresses, int(limit))
|
||||
|
||||
def build_create_openorders_instructions(self) -> CombinableInstructions:
|
||||
create_open_orders = build_create_serum_open_orders_instructions(self.context, self.wallet, self.raw_market)
|
||||
self.open_orders_address = create_open_orders.signers[0].public_key
|
||||
return create_open_orders
|
||||
|
||||
def build_redeem_instructions(self) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return """« 𝚂𝚎𝚛𝚞𝚖𝙼𝚊𝚛𝚔𝚎𝚝𝙸𝚗𝚜𝚝𝚛𝚞𝚌𝚝𝚒𝚘𝚗𝙱𝚞𝚒𝚕𝚍𝚎𝚛 »"""
|
|
@ -17,18 +17,136 @@
|
|||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
from pyserum.market import Market as PySerumMarket
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
from .combinableinstructions import CombinableInstructions
|
||||
from .constants import SYSTEM_PROGRAM_ADDRESS
|
||||
from .context import Context
|
||||
from .marketoperations import MarketOperations
|
||||
from .orders import Order, OrderBook
|
||||
from .instructions import build_create_serum_open_orders_instructions, build_serum_consume_events_instructions, build_serum_settle_instructions, build_serum_place_order_instructions
|
||||
from .marketoperations import MarketInstructionBuilder, MarketOperations
|
||||
from .openorders import OpenOrders
|
||||
from .orders import Order, OrderBook, Side
|
||||
from .publickey import encode_public_key_for_sorting
|
||||
from .serummarket import SerumMarket
|
||||
from .serummarketinstructionbuilder import SerumMarketInstructionBuilder
|
||||
from .token import Instrument, Token
|
||||
from .tokenaccount import TokenAccount
|
||||
from .wallet import Wallet
|
||||
|
||||
|
||||
# # 🥭 SerumMarketInstructionBuilder
|
||||
#
|
||||
# This file deals with building instructions for Serum markets.
|
||||
#
|
||||
# As a matter of policy for all InstructionBuidlers, construction and build_* methods should all work with
|
||||
# existing data, requiring no fetches from Solana or other sources. All necessary data should all be loaded
|
||||
# on initial setup in the `load()` method.
|
||||
#
|
||||
class SerumMarketInstructionBuilder(MarketInstructionBuilder):
|
||||
def __init__(self, context: Context, wallet: Wallet, serum_market: SerumMarket, raw_market: PySerumMarket, base_token_account: TokenAccount, quote_token_account: TokenAccount, open_orders_address: typing.Optional[PublicKey], fee_discount_token_address: typing.Optional[PublicKey]):
|
||||
super().__init__()
|
||||
self.context: Context = context
|
||||
self.wallet: Wallet = wallet
|
||||
self.serum_market: SerumMarket = serum_market
|
||||
self.raw_market: PySerumMarket = raw_market
|
||||
self.base_token_account: TokenAccount = base_token_account
|
||||
self.quote_token_account: TokenAccount = quote_token_account
|
||||
self.open_orders_address: typing.Optional[PublicKey] = open_orders_address
|
||||
self.fee_discount_token_address: typing.Optional[PublicKey] = fee_discount_token_address
|
||||
|
||||
@staticmethod
|
||||
def load(context: Context, wallet: Wallet, serum_market: SerumMarket) -> "SerumMarketInstructionBuilder":
|
||||
raw_market: PySerumMarket = PySerumMarket.load(
|
||||
context.client.compatible_client, serum_market.address, context.serum_program_address)
|
||||
|
||||
fee_discount_token_address: typing.Optional[PublicKey] = None
|
||||
srm_instrument: typing.Optional[Instrument] = context.instrument_lookup.find_by_symbol("SRM")
|
||||
if srm_instrument is not None:
|
||||
srm_token: Token = Token.ensure(srm_instrument)
|
||||
fee_discount_token_account = TokenAccount.fetch_largest_for_owner_and_token(
|
||||
context, wallet.address, srm_token)
|
||||
if fee_discount_token_account is not None:
|
||||
fee_discount_token_address = fee_discount_token_account.address
|
||||
|
||||
open_orders_address: typing.Optional[PublicKey] = None
|
||||
all_open_orders = OpenOrders.load_for_market_and_owner(
|
||||
context, serum_market.address, wallet.address, context.serum_program_address, serum_market.base.decimals, serum_market.quote.decimals)
|
||||
if len(all_open_orders) > 0:
|
||||
open_orders_address = all_open_orders[0].address
|
||||
|
||||
base_token_account = TokenAccount.fetch_largest_for_owner_and_token(context, wallet.address, serum_market.base)
|
||||
if base_token_account is None:
|
||||
raise Exception(f"Could not find source token account for base token {serum_market.base.symbol}.")
|
||||
|
||||
quote_token_account = TokenAccount.fetch_largest_for_owner_and_token(
|
||||
context, wallet.address, serum_market.quote)
|
||||
if quote_token_account is None:
|
||||
raise Exception(f"Could not find source token account for quote token {serum_market.quote.symbol}.")
|
||||
|
||||
return SerumMarketInstructionBuilder(context, wallet, serum_market, raw_market, base_token_account, quote_token_account, open_orders_address, fee_discount_token_address)
|
||||
|
||||
def build_cancel_order_instructions(self, order: Order, ok_if_missing: bool = False) -> CombinableInstructions:
|
||||
# For us to cancel an order, an open_orders account must already exist (or have existed).
|
||||
if self.open_orders_address is None:
|
||||
raise Exception(f"Cannot cancel order with client ID {order.client_id} - no OpenOrders account.")
|
||||
|
||||
raw_instruction = self.raw_market.make_cancel_order_by_client_id_instruction(
|
||||
self.wallet.keypair, self.open_orders_address, order.client_id
|
||||
)
|
||||
return CombinableInstructions.from_instruction(raw_instruction)
|
||||
|
||||
def build_place_order_instructions(self, order: Order) -> CombinableInstructions:
|
||||
ensure_open_orders = CombinableInstructions.empty()
|
||||
if self.open_orders_address is None:
|
||||
ensure_open_orders = self.build_create_openorders_instructions()
|
||||
|
||||
if self.open_orders_address is None:
|
||||
raise Exception("Failed to find or create OpenOrders address")
|
||||
|
||||
payer_token_account = self.quote_token_account if order.side == Side.BUY else self.base_token_account
|
||||
place = build_serum_place_order_instructions(self.context, self.wallet, self.raw_market,
|
||||
payer_token_account.address, self.open_orders_address,
|
||||
order.order_type, order.side, order.price,
|
||||
order.quantity, order.client_id,
|
||||
self.fee_discount_token_address)
|
||||
|
||||
return ensure_open_orders + place
|
||||
|
||||
def build_settle_instructions(self) -> CombinableInstructions:
|
||||
if self.open_orders_address is None:
|
||||
return CombinableInstructions.empty()
|
||||
return build_serum_settle_instructions(self.context, self.wallet, self.raw_market, self.open_orders_address, self.base_token_account.address, self.quote_token_account.address)
|
||||
|
||||
def build_crank_instructions(self, open_orders_addresses: typing.Sequence[PublicKey], limit: Decimal = Decimal(32)) -> CombinableInstructions:
|
||||
if self.open_orders_address is None:
|
||||
self.logger.debug("Returning empty crank instructions - no serum OpenOrders address provided.")
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
open_orders_to_crank: typing.Sequence[PublicKey] = [*open_orders_addresses, self.open_orders_address]
|
||||
distinct_open_orders_addresses: typing.List[PublicKey] = []
|
||||
for oo in open_orders_to_crank:
|
||||
if oo not in distinct_open_orders_addresses:
|
||||
distinct_open_orders_addresses += [oo]
|
||||
|
||||
limited_open_orders_addresses = distinct_open_orders_addresses[0:min(
|
||||
int(limit), len(distinct_open_orders_addresses))]
|
||||
|
||||
limited_open_orders_addresses.sort(key=encode_public_key_for_sorting)
|
||||
|
||||
return build_serum_consume_events_instructions(self.context, self.serum_market.address, self.raw_market.state.event_queue(), limited_open_orders_addresses, int(limit))
|
||||
|
||||
def build_create_openorders_instructions(self) -> CombinableInstructions:
|
||||
create_open_orders = build_create_serum_open_orders_instructions(self.context, self.wallet, self.raw_market)
|
||||
self.open_orders_address = create_open_orders.signers[0].public_key
|
||||
return create_open_orders
|
||||
|
||||
def build_redeem_instructions(self) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return """« 𝚂𝚎𝚛𝚞𝚖𝙼𝚊𝚛𝚔𝚎𝚝𝙸𝚗𝚜𝚝𝚛𝚞𝚌𝚝𝚒𝚘𝚗𝙱𝚞𝚒𝚕𝚍𝚎𝚛 »"""
|
||||
|
||||
|
||||
# # 🥭 SerumMarketOperations class
|
||||
#
|
||||
# This class performs standard operations on the Serum orderbook.
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
# # ⚠ 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)
|
||||
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
from pyserum.market import Market as PySerumMarket
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
from .account import Account
|
||||
from .combinableinstructions import CombinableInstructions
|
||||
from .context import Context
|
||||
from .group import Group
|
||||
from .instructions import build_serum_consume_events_instructions, build_spot_place_order_instructions, build_cancel_spot_order_instructions, build_spot_settle_instructions, build_spot_openorders_instructions
|
||||
from .marketinstructionbuilder import MarketInstructionBuilder
|
||||
from .orders import Order
|
||||
from .publickey import encode_public_key_for_sorting
|
||||
from .spotmarket import SpotMarket
|
||||
from .wallet import Wallet
|
||||
|
||||
|
||||
# # 🥭 SpotMarketInstructionBuilder
|
||||
#
|
||||
# This file deals with building instructions for Spot markets.
|
||||
#
|
||||
# As a matter of policy for all InstructionBuidlers, construction and build_* methods should all work with
|
||||
# existing data, requiring no fetches from Solana or other sources. All necessary data should all be loaded
|
||||
# on initial setup in the `load()` method.
|
||||
#
|
||||
|
||||
class SpotMarketInstructionBuilder(MarketInstructionBuilder):
|
||||
def __init__(self, context: Context, wallet: Wallet, group: Group, account: Account, spot_market: SpotMarket, raw_market: PySerumMarket, market_index: int, fee_discount_token_address: PublicKey):
|
||||
super().__init__()
|
||||
self.context: Context = context
|
||||
self.wallet: Wallet = wallet
|
||||
self.group: Group = group
|
||||
self.account: Account = account
|
||||
self.spot_market: SpotMarket = spot_market
|
||||
self.raw_market: PySerumMarket = raw_market
|
||||
self.market_index: int = market_index
|
||||
self.fee_discount_token_address: PublicKey = fee_discount_token_address
|
||||
|
||||
self.open_orders_address: typing.Optional[PublicKey] = self.account.spot_open_orders_by_index[self.market_index]
|
||||
|
||||
@staticmethod
|
||||
def load(context: Context, wallet: Wallet, group: Group, account: Account, spot_market: SpotMarket) -> "SpotMarketInstructionBuilder":
|
||||
raw_market: PySerumMarket = PySerumMarket.load(
|
||||
context.client.compatible_client, spot_market.address, context.serum_program_address)
|
||||
|
||||
msrm_balance = context.client.get_token_account_balance(group.msrm_vault)
|
||||
fee_discount_token_address: PublicKey
|
||||
if msrm_balance > 0:
|
||||
fee_discount_token_address = group.msrm_vault
|
||||
logging.debug(
|
||||
f"MSRM balance is: {msrm_balance} - using MSRM fee discount address {fee_discount_token_address}")
|
||||
else:
|
||||
fee_discount_token_address = group.srm_vault
|
||||
logging.debug(
|
||||
f"MSRM balance is: {msrm_balance} - using SRM fee discount address {fee_discount_token_address}")
|
||||
|
||||
market_index = group.find_spot_market_index(spot_market.address)
|
||||
|
||||
return SpotMarketInstructionBuilder(context, wallet, group, account, spot_market, raw_market, market_index, fee_discount_token_address)
|
||||
|
||||
def build_cancel_order_instructions(self, order: Order, ok_if_missing: bool = False) -> CombinableInstructions:
|
||||
if self.open_orders_address is None:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
return build_cancel_spot_order_instructions(
|
||||
self.context, self.wallet, self.group, self.account, self.raw_market, order, self.open_orders_address)
|
||||
|
||||
def build_place_order_instructions(self, order: Order) -> CombinableInstructions:
|
||||
return build_spot_place_order_instructions(self.context, self.wallet, self.group, self.account,
|
||||
self.raw_market, order.order_type, order.side, order.price,
|
||||
order.quantity, order.client_id,
|
||||
self.fee_discount_token_address)
|
||||
|
||||
def build_settle_instructions(self) -> CombinableInstructions:
|
||||
if self.open_orders_address is None:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
base_rootbank = self.group.find_token_info_by_token(self.spot_market.base).root_bank
|
||||
base_nodebank = base_rootbank.pick_node_bank(self.context)
|
||||
quote_rootbank = self.group.find_token_info_by_token(self.spot_market.quote).root_bank
|
||||
quote_nodebank = quote_rootbank.pick_node_bank(self.context)
|
||||
return build_spot_settle_instructions(self.context, self.wallet, self.account,
|
||||
self.raw_market, self.group, self.open_orders_address,
|
||||
base_rootbank, base_nodebank, quote_rootbank, quote_nodebank)
|
||||
|
||||
def build_crank_instructions(self, open_orders_addresses: typing.Sequence[PublicKey], limit: Decimal = Decimal(32)) -> CombinableInstructions:
|
||||
if self.open_orders_address is None:
|
||||
self.logger.debug("Returning empty crank instructions - no spot OpenOrders address provided.")
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
open_orders_to_crank: typing.Sequence[PublicKey] = [*open_orders_addresses, self.open_orders_address]
|
||||
distinct_open_orders_addresses: typing.List[PublicKey] = []
|
||||
for oo in open_orders_to_crank:
|
||||
if oo not in distinct_open_orders_addresses:
|
||||
distinct_open_orders_addresses += [oo]
|
||||
|
||||
limited_open_orders_addresses = distinct_open_orders_addresses[0:min(
|
||||
int(limit), len(distinct_open_orders_addresses))]
|
||||
|
||||
limited_open_orders_addresses.sort(key=encode_public_key_for_sorting)
|
||||
|
||||
return build_serum_consume_events_instructions(self.context, self.spot_market.address, self.raw_market.state.event_queue(), limited_open_orders_addresses, int(limit))
|
||||
|
||||
def build_redeem_instructions(self) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_create_openorders_instructions(self) -> CombinableInstructions:
|
||||
return build_spot_openorders_instructions(self.context, self.wallet, self.group, self.account, self.raw_market)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« 𝚂𝚙𝚘𝚝𝙼𝚊𝚛𝚔𝚎𝚝𝙸𝚗𝚜𝚝𝚛𝚞𝚌𝚝𝚒𝚘𝚗𝙱𝚞𝚒𝚕𝚍𝚎𝚛 [{self.spot_market.symbol}] »"
|
|
@ -13,10 +13,11 @@
|
|||
# [Github](https://github.com/blockworks-foundation)
|
||||
# [Email](mailto:hello@blockworks.foundation)
|
||||
|
||||
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
from pyserum.market import Market as PySerumMarket
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
from .account import Account
|
||||
|
@ -24,13 +25,109 @@ from .combinableinstructions import CombinableInstructions
|
|||
from .constants import SYSTEM_PROGRAM_ADDRESS
|
||||
from .context import Context
|
||||
from .group import Group
|
||||
from .marketoperations import MarketOperations
|
||||
from .instructions import build_serum_consume_events_instructions, build_spot_place_order_instructions, build_cancel_spot_order_instructions, build_spot_settle_instructions, build_spot_openorders_instructions
|
||||
from .marketoperations import MarketInstructionBuilder, MarketOperations
|
||||
from .orders import Order, OrderBook
|
||||
from .publickey import encode_public_key_for_sorting
|
||||
from .spotmarket import SpotMarket
|
||||
from .spotmarketinstructionbuilder import SpotMarketInstructionBuilder
|
||||
from .wallet import Wallet
|
||||
|
||||
|
||||
# # 🥭 SpotMarketInstructionBuilder
|
||||
#
|
||||
# This file deals with building instructions for Spot markets.
|
||||
#
|
||||
# As a matter of policy for all InstructionBuidlers, construction and build_* methods should all work with
|
||||
# existing data, requiring no fetches from Solana or other sources. All necessary data should all be loaded
|
||||
# on initial setup in the `load()` method.
|
||||
#
|
||||
class SpotMarketInstructionBuilder(MarketInstructionBuilder):
|
||||
def __init__(self, context: Context, wallet: Wallet, group: Group, account: Account, spot_market: SpotMarket, raw_market: PySerumMarket, market_index: int, fee_discount_token_address: PublicKey):
|
||||
super().__init__()
|
||||
self.context: Context = context
|
||||
self.wallet: Wallet = wallet
|
||||
self.group: Group = group
|
||||
self.account: Account = account
|
||||
self.spot_market: SpotMarket = spot_market
|
||||
self.raw_market: PySerumMarket = raw_market
|
||||
self.market_index: int = market_index
|
||||
self.fee_discount_token_address: PublicKey = fee_discount_token_address
|
||||
|
||||
self.open_orders_address: typing.Optional[PublicKey] = self.account.spot_open_orders_by_index[self.market_index]
|
||||
|
||||
@staticmethod
|
||||
def load(context: Context, wallet: Wallet, group: Group, account: Account, spot_market: SpotMarket) -> "SpotMarketInstructionBuilder":
|
||||
raw_market: PySerumMarket = PySerumMarket.load(
|
||||
context.client.compatible_client, spot_market.address, context.serum_program_address)
|
||||
|
||||
msrm_balance = context.client.get_token_account_balance(group.msrm_vault)
|
||||
fee_discount_token_address: PublicKey
|
||||
if msrm_balance > 0:
|
||||
fee_discount_token_address = group.msrm_vault
|
||||
logging.debug(
|
||||
f"MSRM balance is: {msrm_balance} - using MSRM fee discount address {fee_discount_token_address}")
|
||||
else:
|
||||
fee_discount_token_address = group.srm_vault
|
||||
logging.debug(
|
||||
f"MSRM balance is: {msrm_balance} - using SRM fee discount address {fee_discount_token_address}")
|
||||
|
||||
market_index = group.find_spot_market_index(spot_market.address)
|
||||
|
||||
return SpotMarketInstructionBuilder(context, wallet, group, account, spot_market, raw_market, market_index, fee_discount_token_address)
|
||||
|
||||
def build_cancel_order_instructions(self, order: Order, ok_if_missing: bool = False) -> CombinableInstructions:
|
||||
if self.open_orders_address is None:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
return build_cancel_spot_order_instructions(
|
||||
self.context, self.wallet, self.group, self.account, self.raw_market, order, self.open_orders_address)
|
||||
|
||||
def build_place_order_instructions(self, order: Order) -> CombinableInstructions:
|
||||
return build_spot_place_order_instructions(self.context, self.wallet, self.group, self.account,
|
||||
self.raw_market, order.order_type, order.side, order.price,
|
||||
order.quantity, order.client_id,
|
||||
self.fee_discount_token_address)
|
||||
|
||||
def build_settle_instructions(self) -> CombinableInstructions:
|
||||
if self.open_orders_address is None:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
base_rootbank = self.group.find_token_info_by_token(self.spot_market.base).root_bank
|
||||
base_nodebank = base_rootbank.pick_node_bank(self.context)
|
||||
quote_rootbank = self.group.find_token_info_by_token(self.spot_market.quote).root_bank
|
||||
quote_nodebank = quote_rootbank.pick_node_bank(self.context)
|
||||
return build_spot_settle_instructions(self.context, self.wallet, self.account,
|
||||
self.raw_market, self.group, self.open_orders_address,
|
||||
base_rootbank, base_nodebank, quote_rootbank, quote_nodebank)
|
||||
|
||||
def build_crank_instructions(self, open_orders_addresses: typing.Sequence[PublicKey], limit: Decimal = Decimal(32)) -> CombinableInstructions:
|
||||
if self.open_orders_address is None:
|
||||
self.logger.debug("Returning empty crank instructions - no spot OpenOrders address provided.")
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
open_orders_to_crank: typing.Sequence[PublicKey] = [*open_orders_addresses, self.open_orders_address]
|
||||
distinct_open_orders_addresses: typing.List[PublicKey] = []
|
||||
for oo in open_orders_to_crank:
|
||||
if oo not in distinct_open_orders_addresses:
|
||||
distinct_open_orders_addresses += [oo]
|
||||
|
||||
limited_open_orders_addresses = distinct_open_orders_addresses[0:min(
|
||||
int(limit), len(distinct_open_orders_addresses))]
|
||||
|
||||
limited_open_orders_addresses.sort(key=encode_public_key_for_sorting)
|
||||
|
||||
return build_serum_consume_events_instructions(self.context, self.spot_market.address, self.raw_market.state.event_queue(), limited_open_orders_addresses, int(limit))
|
||||
|
||||
def build_redeem_instructions(self) -> CombinableInstructions:
|
||||
return CombinableInstructions.empty()
|
||||
|
||||
def build_create_openorders_instructions(self) -> CombinableInstructions:
|
||||
return build_spot_openorders_instructions(self.context, self.wallet, self.group, self.account, self.raw_market)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« 𝚂𝚙𝚘𝚝𝙼𝚊𝚛𝚔𝚎𝚝𝙸𝚗𝚜𝚝𝚛𝚞𝚌𝚝𝚒𝚘𝚗𝙱𝚞𝚒𝚕𝚍𝚎𝚛 [{self.spot_market.symbol}] »"
|
||||
|
||||
|
||||
# # 🥭 SpotMarketOperations class
|
||||
#
|
||||
# This class puts trades on the Serum orderbook. It doesn't do anything complicated.
|
||||
|
|
|
@ -41,8 +41,7 @@ from .perpmarket import PerpMarket
|
|||
from .placedorder import PlacedOrdersContainer
|
||||
from .serummarket import SerumMarket
|
||||
from .spotmarket import SpotMarket
|
||||
from .spotmarketinstructionbuilder import SpotMarketInstructionBuilder
|
||||
from .spotmarketoperations import SpotMarketOperations
|
||||
from .spotmarketoperations import SpotMarketInstructionBuilder, SpotMarketOperations
|
||||
from .tokenaccount import TokenAccount
|
||||
from .token import Instrument, Token
|
||||
from .wallet import Wallet
|
||||
|
|
Loading…
Reference in New Issue