Compare commits
3 Commits
6d401a3966
...
89e45e936e
Author | SHA1 | Date |
---|---|---|
Geoff Taylor | 89e45e936e | |
Geoff Taylor | f1ea12e30f | |
Geoff Taylor | a1be4a68db |
|
@ -53,7 +53,9 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
|||
)
|
||||
orders = market_operations.load_my_orders(include_expired=True)
|
||||
if len(orders) == 0:
|
||||
mango.output(f"No open orders on {market_operations.market.symbol}")
|
||||
mango.output(
|
||||
f"No open orders on {market_operations.market.fully_qualified_symbol}"
|
||||
)
|
||||
else:
|
||||
if mango.PerpMarket.isa(market_operations.market):
|
||||
cancel_all = mango.PerpMarketOperations.ensure(
|
||||
|
|
|
@ -181,10 +181,12 @@ def cleanup(
|
|||
dry_run: bool,
|
||||
) -> None:
|
||||
market_operations: mango.MarketOperations = mango.operations(
|
||||
context, wallet, account, market.symbol, dry_run
|
||||
context, wallet, account, market.fully_qualified_symbol, dry_run
|
||||
)
|
||||
market_instruction_builder: mango.MarketInstructionBuilder = (
|
||||
mango.instruction_builder(context, wallet, account, market.symbol, dry_run)
|
||||
mango.instruction_builder(
|
||||
context, wallet, account, market.fully_qualified_symbol, dry_run
|
||||
)
|
||||
)
|
||||
cancels: mango.CombinableInstructions = mango.CombinableInstructions.empty()
|
||||
orders = market_operations.load_my_orders(include_expired=True)
|
||||
|
@ -221,7 +223,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
|||
# The market index is also the index of the base token in the group's token list.
|
||||
if market.quote != group.shared_quote_token:
|
||||
raise Exception(
|
||||
f"Group {group.name} uses shared quote token {group.shared_quote_token.symbol}/{group.shared_quote_token.mint}, but market {market.symbol} uses quote token {market.quote.symbol}/{market.quote.mint}."
|
||||
f"Group {group.name} uses shared quote token {group.shared_quote_token.symbol}/{group.shared_quote_token.mint}, but market {market.fully_qualified_symbol} uses quote token {market.quote.symbol}/{market.quote.mint}."
|
||||
)
|
||||
|
||||
cleanup(context, wallet, account, market, args.dry_run)
|
||||
|
@ -230,7 +232,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
|||
if args.hedging_market is not None:
|
||||
if not mango.PerpMarket.isa(market):
|
||||
raise Exception(
|
||||
f"Cannot hedge - market {market.symbol} is not a perp market."
|
||||
f"Cannot hedge - market {market.fully_qualified_symbol} is not a perp market."
|
||||
)
|
||||
|
||||
underlying_market = mango.PerpMarket.ensure(market)
|
||||
|
@ -243,7 +245,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
|||
f"MarketOperations for {args.hedging_market} is not a SpotMarketOperations."
|
||||
)
|
||||
|
||||
logging.info(f"Hedging on {hedging_ops.market.symbol}")
|
||||
logging.info(f"Hedging on {hedging_ops.market.fully_qualified_symbol}")
|
||||
|
||||
target_balance: typing.Optional[
|
||||
mango.TargetBalance
|
||||
|
@ -287,7 +289,9 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
|||
logging.info(f"Desired orders chain: {desired_orders_chain}")
|
||||
|
||||
market_instruction_builder: mango.MarketInstructionBuilder = (
|
||||
mango.instruction_builder(context, wallet, account, market.symbol, args.dry_run)
|
||||
mango.instruction_builder(
|
||||
context, wallet, account, market.fully_qualified_symbol, args.dry_run
|
||||
)
|
||||
)
|
||||
|
||||
market_maker = mango.marketmaking.MarketMaker(
|
||||
|
@ -310,7 +314,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
|||
oracle = oracle_provider.oracle_for_market(context, oracle_market)
|
||||
if oracle is None:
|
||||
raise Exception(
|
||||
f"Could not find oracle for market {oracle_market.symbol} from provider {args.oracle_provider}."
|
||||
f"Could not find oracle for market {oracle_market.fully_qualified_symbol} from provider {args.oracle_provider}."
|
||||
)
|
||||
|
||||
model_state_builder: mango.marketmaking.ModelStateBuilder = (
|
||||
|
|
|
@ -5,6 +5,7 @@ import logging
|
|||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
from solana.publickey import PublicKey
|
||||
|
@ -23,7 +24,13 @@ parser.add_argument(
|
|||
help="Destination address for the SPL token - can be either the actual token address or the address of the owner of the token address",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--quantity", type=Decimal, required=True, help="quantity of token to send"
|
||||
"--quantity", type=Decimal, required=False, help="quantity of token to send"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--wallet-target",
|
||||
type=Decimal,
|
||||
required=False,
|
||||
help="wallet balance of token to target by sending remainder",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
|
@ -39,16 +46,69 @@ parser.add_argument(
|
|||
)
|
||||
args: argparse.Namespace = mango.parse_args(parser)
|
||||
|
||||
|
||||
def __quantity_from_wallet_target(
|
||||
context: mango.Context,
|
||||
signers: mango.CombinableInstructions,
|
||||
token: mango.Token,
|
||||
balance: Decimal,
|
||||
to_keep: typing.Optional[Decimal],
|
||||
) -> typing.Optional[Decimal]:
|
||||
if to_keep is None:
|
||||
return None
|
||||
|
||||
# To accurately calculate the cost we need to build the transaction we're going to send.
|
||||
#
|
||||
# But we don't know the quantity we're sending until we know the cost.
|
||||
#
|
||||
# So here we but together a fake transaction for sending zero SOL from
|
||||
# SYSTEM_PROGRAM_ADDRESS to SYSTEM_PROGRAM_ADDRESS, and use that to calculate the cost so
|
||||
# we can take the cost into account when figuring out how much to send.
|
||||
#
|
||||
# (Using SYSTEM_PROGRAM_ADDRESS and zero shouldn't affect the calculations but that may
|
||||
# change when getFeeForMessage() is used.)
|
||||
params = TransferParams(
|
||||
from_pubkey=mango.SYSTEM_PROGRAM_ADDRESS,
|
||||
to_pubkey=mango.SYSTEM_PROGRAM_ADDRESS,
|
||||
lamports=0,
|
||||
)
|
||||
fake_instruction = mango.CombinableInstructions.from_instruction(transfer(params))
|
||||
cost = (signers + fake_instruction).cost_to_execute(context)
|
||||
|
||||
to_send = balance - to_keep - cost
|
||||
if to_send < 0:
|
||||
raise Exception(
|
||||
f"Cannot achieve wallet balance target of {to_keep:,.8f} {token.symbol} by depositing - wallet only has balance of {balance:,.8f} {token.symbol}"
|
||||
)
|
||||
|
||||
return token.round(to_send, mango.RoundDirection.DOWN)
|
||||
|
||||
|
||||
with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
||||
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
|
||||
|
||||
logging.info(f"Wallet address: {wallet.address}")
|
||||
|
||||
token = mango.SolToken
|
||||
sol_balance = context.client.get_balance(wallet.address)
|
||||
mango.output(f"Balance: {sol_balance} SOL")
|
||||
|
||||
signers: mango.CombinableInstructions = mango.CombinableInstructions.from_wallet(
|
||||
wallet
|
||||
)
|
||||
quantity: typing.Optional[Decimal] = args.quantity or __quantity_from_wallet_target(
|
||||
context, signers, token, sol_balance, args.wallet_target
|
||||
)
|
||||
if quantity is None:
|
||||
raise Exception(
|
||||
"Neither --quantity nor --wallet-target were specified - must specify one (and only one) of those parameters"
|
||||
)
|
||||
|
||||
if quantity < 0:
|
||||
raise Exception(f"Cannot send negative quantity {quantity:,.8f} {token.symbol}")
|
||||
|
||||
# "A lamport has a value of 0.000000001 SOL." from https://docs.solana.com/introduction
|
||||
lamports = int(args.quantity * mango.SOL_DECIMAL_DIVISOR)
|
||||
lamports = int(quantity * mango.SOL_DECIMAL_DIVISOR)
|
||||
source = wallet.address
|
||||
destination = args.address
|
||||
|
||||
|
|
|
@ -25,7 +25,13 @@ parser.add_argument(
|
|||
help="Destination address for the SPL token - can be either the actual token address or the address of the owner of the token address",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--quantity", type=Decimal, required=True, help="quantity of token to send"
|
||||
"--quantity", type=Decimal, required=False, help="quantity of token to send"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--wallet-target",
|
||||
type=Decimal,
|
||||
required=False,
|
||||
help="wallet balance of token to target by sending remainder",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--wait",
|
||||
|
@ -41,6 +47,29 @@ parser.add_argument(
|
|||
)
|
||||
args: argparse.Namespace = mango.parse_args(parser)
|
||||
|
||||
|
||||
def __quantity_from_wallet_target(
|
||||
context: mango.Context,
|
||||
wallet: mango.Wallet,
|
||||
token: mango.Token,
|
||||
to_keep: typing.Optional[Decimal],
|
||||
) -> typing.Optional[Decimal]:
|
||||
if to_keep is None:
|
||||
return None
|
||||
|
||||
token_accounts: typing.Sequence[
|
||||
mango.TokenAccount
|
||||
] = mango.TokenAccount.fetch_all_for_owner_and_token(context, wallet.address, token)
|
||||
total = sum(acc.value.value for acc in token_accounts)
|
||||
to_deposit = total - to_keep
|
||||
if to_deposit < 0:
|
||||
raise Exception(
|
||||
f"Cannot achieve wallet balance target of {to_keep:,.8f} {token.symbol} by sending - wallet only has balance of {total:,.8f} {token.symbol}"
|
||||
)
|
||||
|
||||
return token.round(to_deposit, mango.RoundDirection.DOWN)
|
||||
|
||||
|
||||
with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
||||
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
|
||||
token: mango.Token = mango.token(context, args.symbol)
|
||||
|
@ -86,18 +115,27 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
|||
f"Account {args.address} is neither a root wallet account nor an SPL token account."
|
||||
)
|
||||
|
||||
mango.output("Balance:", source.value)
|
||||
|
||||
quantity: typing.Optional[Decimal] = args.quantity or __quantity_from_wallet_target(
|
||||
context, wallet, token, args.wallet_target
|
||||
)
|
||||
if quantity is None:
|
||||
raise Exception(
|
||||
"Neither --quantity nor --wallet-target were specified - must specify one (and only one) of those parameters"
|
||||
)
|
||||
|
||||
if quantity < 0:
|
||||
raise Exception(f"Cannot send negative quantity {quantity:,.8f} {token.symbol}")
|
||||
|
||||
signers: mango.CombinableInstructions = mango.CombinableInstructions.from_wallet(
|
||||
wallet
|
||||
)
|
||||
transfer = mango.build_spl_transfer_tokens_instructions(
|
||||
context, wallet, token, source.address, destination, args.quantity
|
||||
context, wallet, token, source.address, destination, quantity
|
||||
)
|
||||
|
||||
mango.output(
|
||||
"Balance:",
|
||||
source,
|
||||
)
|
||||
amount = token.shift_to_native(args.quantity)
|
||||
amount = token.shift_to_native(quantity)
|
||||
text_amount = f"{amount} {token.name} (@ {token.decimals} decimal places)"
|
||||
creating_marker = "" if create_ata.is_empty else " *CREATING*"
|
||||
mango.output(f"Sending {text_amount}")
|
||||
|
|
|
@ -50,7 +50,7 @@ with mango.ContextBuilder.from_command_line_parameters(
|
|||
oracle = oracle_provider.oracle_for_market(context, market)
|
||||
if oracle is None:
|
||||
raise Exception(
|
||||
f"Could not find oracle for market {market.symbol} from provider {args.oracle_provider}."
|
||||
f"Could not find oracle for market {market.fully_qualified_symbol} from provider {args.oracle_provider}."
|
||||
)
|
||||
|
||||
health_check = mango.HealthCheck()
|
||||
|
|
|
@ -38,7 +38,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
|||
oracle = oracle_provider.oracle_for_market(context, market)
|
||||
if oracle is None:
|
||||
mango.output(
|
||||
f"Could not find oracle for market {market.symbol} from provider {args.provider}."
|
||||
f"Could not find oracle for market {market.fully_qualified_symbol} from provider {args.provider}."
|
||||
)
|
||||
else:
|
||||
if not args.stream:
|
||||
|
|
|
@ -41,7 +41,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
|||
market.quote,
|
||||
)
|
||||
mango.output(
|
||||
f"Found {len(all_open_orders_for_market)} Serum OpenOrders account(s) for market {market.symbol}."
|
||||
f"Found {len(all_open_orders_for_market)} Serum OpenOrders account(s) for market {market.fully_qualified_symbol}."
|
||||
)
|
||||
for open_orders in all_open_orders_for_market:
|
||||
mango.output(open_orders)
|
||||
|
|
|
@ -38,6 +38,11 @@ parser.add_argument(
|
|||
type=PublicKey,
|
||||
help="address of the specific account to use, if more than one available",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--destination-wallet",
|
||||
type=PublicKey,
|
||||
help="if specified, the wallet to which the withdrawal should be sent. (Defaults to the current wallet.)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-borrow",
|
||||
action="store_true",
|
||||
|
@ -160,8 +165,9 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context:
|
|||
if args.dry_run:
|
||||
mango.output("Dry run - not sending transaction")
|
||||
else:
|
||||
destination = args.destination_wallet or wallet.address
|
||||
signatures = account.withdraw(
|
||||
context, wallet, withdrawal_value, args.allow_borrow
|
||||
context, wallet, destination, withdrawal_value, args.allow_borrow
|
||||
)
|
||||
|
||||
if args.wait:
|
||||
|
|
|
@ -24,6 +24,7 @@ from .accountinfo import AccountInfo
|
|||
from .addressableaccount import AddressableAccount
|
||||
from .cache import Cache, PerpMarketCache, RootBankCache, MarketCache
|
||||
from .combinableinstructions import CombinableInstructions
|
||||
from .constants import SYSTEM_PROGRAM_ADDRESS
|
||||
from .context import Context
|
||||
from .encoding import encode_key
|
||||
from .group import Group, GroupSlot, GroupSlotPerpMarket
|
||||
|
@ -638,12 +639,25 @@ class Account(AddressableAccount):
|
|||
self,
|
||||
context: Context,
|
||||
wallet: Wallet,
|
||||
destination: PublicKey,
|
||||
value: InstrumentValue,
|
||||
allow_borrow: bool,
|
||||
) -> typing.Sequence[str]:
|
||||
destination_info: typing.Optional[AccountInfo] = AccountInfo.load(
|
||||
context, destination
|
||||
)
|
||||
if destination_info is None:
|
||||
raise Exception(f"Could not find wallet at address {destination}.")
|
||||
|
||||
if destination_info.owner != SYSTEM_PROGRAM_ADDRESS:
|
||||
# This is not a root wallet account
|
||||
raise Exception(
|
||||
f"Can't withdraw to address {destination} - not a wallet address."
|
||||
)
|
||||
|
||||
token: Token = Token.ensure(value.token)
|
||||
token_account = TokenAccount.fetch_largest_for_owner_and_token(
|
||||
context, wallet.address, token
|
||||
context, destination, token
|
||||
)
|
||||
|
||||
withdrawal_token_account: TokenAccount
|
||||
|
@ -653,7 +667,7 @@ class Account(AddressableAccount):
|
|||
create_ata,
|
||||
token_account,
|
||||
) = build_create_associated_instructions_and_account(
|
||||
context, wallet, wallet.address, token
|
||||
context, wallet, destination, token
|
||||
)
|
||||
|
||||
withdrawal_token_account = TokenAccount(
|
||||
|
|
|
@ -16,12 +16,15 @@
|
|||
import logging
|
||||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
from solana.blockhash import Blockhash
|
||||
from solana.keypair import Keypair
|
||||
from solana.publickey import PublicKey
|
||||
from solana.transaction import Transaction, TransactionInstruction
|
||||
from solana.utils import shortvec_encoding as shortvec
|
||||
|
||||
from mango.constants import SOL_DECIMAL_DIVISOR
|
||||
|
||||
from .context import Context
|
||||
from .instructionreporter import InstructionReporter
|
||||
from .wallet import Wallet
|
||||
|
@ -314,6 +317,38 @@ class CombinableInstructions:
|
|||
) -> typing.Sequence[str]:
|
||||
return self.execute(context, on_exception_continue)
|
||||
|
||||
def cost_to_execute(self, context: Context) -> Decimal:
|
||||
# getFees() is depracated and will be replaced by getFeeForMessage() at some point.
|
||||
# getFeeForMessage() is not fully available yet though.
|
||||
fee_response = context.client.compatible_client.get_fees()
|
||||
# fee_response should look like:
|
||||
# {
|
||||
# "jsonrpc": "2.0",
|
||||
# "result": {
|
||||
# "context": {
|
||||
# "slot": 1
|
||||
# },
|
||||
# "value": {
|
||||
# "blockhash": "CSymwgTNX1j3E4qhKfJAUE41nBWEwXufoYryPbkde5RR",
|
||||
# "feeCalculator": {
|
||||
# "lamportsPerSignature": 5000
|
||||
# },
|
||||
# "lastValidSlot": 297,
|
||||
# "lastValidBlockHeight": 296
|
||||
# }
|
||||
# },
|
||||
# "id": 1
|
||||
# }
|
||||
number_of_signatures = len(self.signers)
|
||||
lamports_per_signature = fee_response["result"]["value"]["feeCalculator"][
|
||||
"lamportsPerSignature"
|
||||
]
|
||||
|
||||
fee_in_lamports = Decimal(number_of_signatures) * Decimal(
|
||||
lamports_per_signature
|
||||
)
|
||||
return fee_in_lamports / SOL_DECIMAL_DIVISOR
|
||||
|
||||
def __str__(self) -> str:
|
||||
report: typing.List[str] = []
|
||||
for index, signer in enumerate(self.signers):
|
||||
|
|
|
@ -45,14 +45,14 @@ class PerpToSpotHedger(Hedger):
|
|||
underlying_market.quote != hedging_market.quote
|
||||
):
|
||||
raise Exception(
|
||||
f"Market {hedging_market.symbol} cannot be used to hedge market {underlying_market.symbol}."
|
||||
f"Market {hedging_market.fully_qualified_symbol} cannot be used to hedge market {underlying_market.fully_qualified_symbol}."
|
||||
)
|
||||
|
||||
if not mango.Instrument.symbols_match(
|
||||
target_balance.symbol, hedging_market.base.symbol
|
||||
):
|
||||
raise Exception(
|
||||
f"Cannot target {target_balance.symbol} when hedging on {hedging_market.symbol}"
|
||||
f"Cannot target {target_balance.symbol} when hedging on {hedging_market.fully_qualified_symbol}"
|
||||
)
|
||||
|
||||
self.underlying_market: mango.PerpMarket = underlying_market
|
||||
|
@ -124,7 +124,7 @@ class PerpToSpotHedger(Hedger):
|
|||
perp_position_rounded + token_balance_rounded - self.target_balance
|
||||
)
|
||||
self._logger.debug(
|
||||
f"Delta from {self.underlying_market.symbol} to {self.hedging_market.symbol} is {delta:,.8f} {basket_token.base_instrument.symbol}, action threshold is: {self.action_threshold}"
|
||||
f"Delta from {self.underlying_market.fully_qualified_symbol} to {self.hedging_market.fully_qualified_symbol} is {delta:,.8f} {basket_token.base_instrument.symbol}, action threshold is: {self.action_threshold}"
|
||||
)
|
||||
|
||||
if abs(delta) > self.action_threshold:
|
||||
|
@ -151,14 +151,14 @@ class PerpToSpotHedger(Hedger):
|
|||
side, adjusted_price, quantity, mango.OrderType.IOC
|
||||
)
|
||||
self._logger.info(
|
||||
f"Hedging perp position {perp_position} and token balance {token_balance} with {side} of {quantity:,.8f} at {up_or_down} ({model_state.price}) {adjusted_price:,.8f} on {self.hedging_market.symbol}\n\t{order}"
|
||||
f"Hedging perp position {perp_position} and token balance {token_balance} with {side} of {quantity:,.8f} at {up_or_down} ({model_state.price}) {adjusted_price:,.8f} on {self.hedging_market.fully_qualified_symbol}\n\t{order}"
|
||||
)
|
||||
try:
|
||||
self.market_operations.place_order(order)
|
||||
self.pause_counter = 0
|
||||
except Exception:
|
||||
self._logger.error(
|
||||
f"[{context.name}] Failed to hedge on {self.hedging_market.symbol} using order {order} - {traceback.format_exc()}"
|
||||
f"[{context.name}] Failed to hedge on {self.hedging_market.fully_qualified_symbol} using order {order} - {traceback.format_exc()}"
|
||||
)
|
||||
raise
|
||||
|
||||
|
@ -181,4 +181,4 @@ class PerpToSpotHedger(Hedger):
|
|||
self.pulse_error.on_next(exception)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« PerpToSpotHedger for underlying '{self.underlying_market.symbol}', hedging on '{self.hedging_market.symbol}' »"
|
||||
return f"« PerpToSpotHedger for underlying '{self.underlying_market.fully_qualified_symbol}', hedging on '{self.hedging_market.fully_qualified_symbol}' »"
|
||||
|
|
|
@ -81,7 +81,7 @@ class MarketMaker:
|
|||
|
||||
existing_orders = model_state.current_orders()
|
||||
self._logger.debug(
|
||||
f"""Before reconciliation: all owned orders on current orderbook [{model_state.market.symbol}]:
|
||||
f"""Before reconciliation: all owned orders on current orderbook [{model_state.market.fully_qualified_symbol}]:
|
||||
{mango.indent_collection_as_str(existing_orders)}"""
|
||||
)
|
||||
reconciled = self.order_reconciler.reconcile(
|
||||
|
@ -106,14 +106,16 @@ Ignore:
|
|||
):
|
||||
ids = [f"{ord.id} / {ord.client_id}" for ord in reconciled.to_cancel]
|
||||
self._logger.info(
|
||||
f"Cancelling all orders on {self.market.symbol} - currently {len(ids)}: {ids}"
|
||||
f"Cancelling all orders on {self.market.fully_qualified_symbol} - currently {len(ids)}: {ids}"
|
||||
)
|
||||
cancellations = (
|
||||
self.market_instruction_builder.build_cancel_all_orders_instructions()
|
||||
)
|
||||
else:
|
||||
for to_cancel in reconciled.to_cancel:
|
||||
self._logger.info(f"Cancelling {self.market.symbol} {to_cancel}")
|
||||
self._logger.info(
|
||||
f"Cancelling {self.market.fully_qualified_symbol} {to_cancel}"
|
||||
)
|
||||
cancel = (
|
||||
self.market_instruction_builder.build_cancel_order_instructions(
|
||||
to_cancel, ok_if_missing=True
|
||||
|
@ -129,7 +131,7 @@ Ignore:
|
|||
)
|
||||
|
||||
self._logger.info(
|
||||
f"Placing {self.market.symbol} {to_place_with_client_id}"
|
||||
f"Placing {self.market.fully_qualified_symbol} {to_place_with_client_id}"
|
||||
)
|
||||
place_order = (
|
||||
self.market_instruction_builder.build_place_order_instructions(
|
||||
|
@ -138,8 +140,14 @@ Ignore:
|
|||
)
|
||||
place_orders += place_order
|
||||
|
||||
accounts_to_crank = list(model_state.accounts_to_crank)
|
||||
if self.market_instruction_builder.open_orders_address is not None:
|
||||
accounts_to_crank += [
|
||||
self.market_instruction_builder.open_orders_address
|
||||
]
|
||||
|
||||
crank = self.market_instruction_builder.build_crank_instructions(
|
||||
model_state.accounts_to_crank
|
||||
accounts_to_crank
|
||||
)
|
||||
settle = self.market_instruction_builder.build_settle_instructions()
|
||||
|
||||
|
@ -176,7 +184,7 @@ Ignore:
|
|||
self.pulse_error.on_next(exception)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« MarketMaker for market '{self.market.symbol}' »"""
|
||||
return f"""« MarketMaker for market '{self.market.fully_qualified_symbol}' »"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
||||
|
|
|
@ -61,7 +61,7 @@ class WebsocketModelStateBuilder(ModelStateBuilder):
|
|||
return self.model_state
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« WebsocketModelStateBuilder for market '{self.model_state.market.symbol}' »"
|
||||
return f"« WebsocketModelStateBuilder for market '{self.model_state.market.fully_qualified_symbol}' »"
|
||||
|
||||
|
||||
# # 🥭 PollingModelStateBuilder class
|
||||
|
@ -247,9 +247,7 @@ class SerumPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f"""« SerumPollingModelStateBuilder for market '{self.market.symbol}' »"""
|
||||
)
|
||||
return f"""« SerumPollingModelStateBuilder for market '{self.market.fully_qualified_symbol}' »"""
|
||||
|
||||
|
||||
# # 🥭 SpotPollingModelStateBuilder class
|
||||
|
@ -372,7 +370,7 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« SpotPollingModelStateBuilder for market '{self.market.symbol}' »"""
|
||||
return f"""« SpotPollingModelStateBuilder for market '{self.market.fully_qualified_symbol}' »"""
|
||||
|
||||
|
||||
# # 🥭 PerpPollingModelStateBuilder class
|
||||
|
@ -490,4 +488,4 @@ class PerpPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« PerpPollingModelStateBuilder for market '{self.market.symbol}' »"""
|
||||
return f"""« PerpPollingModelStateBuilder for market '{self.market.fully_qualified_symbol}' »"""
|
||||
|
|
|
@ -97,7 +97,9 @@ def _polling_model_state_builder_factory(
|
|||
group, account, mango.PerpMarket.ensure(market), oracle
|
||||
)
|
||||
else:
|
||||
raise Exception(f"Could not determine type of market {market.symbol}")
|
||||
raise Exception(
|
||||
f"Could not determine type of market {market.fully_qualified_symbol}: {market}"
|
||||
)
|
||||
|
||||
|
||||
def _polling_serum_model_state_builder_factory(
|
||||
|
@ -132,7 +134,7 @@ def _polling_serum_model_state_builder_factory(
|
|||
)
|
||||
if len(all_open_orders) == 0:
|
||||
raise Exception(
|
||||
f"Could not find serum openorders account owned by {wallet.address} for market {market.symbol}."
|
||||
f"Could not find serum openorders account owned by {wallet.address} for market {market.fully_qualified_symbol}."
|
||||
)
|
||||
return SerumPollingModelStateBuilder(
|
||||
all_open_orders[0].address,
|
||||
|
@ -160,7 +162,7 @@ def _polling_spot_model_state_builder_factory(
|
|||
all_open_orders_addresses: typing.Sequence[PublicKey] = account.spot_open_orders
|
||||
if open_orders_address is None:
|
||||
raise Exception(
|
||||
f"Could not find spot openorders in account {account.address} for market {market.symbol}."
|
||||
f"Could not find spot openorders in account {account.address} for market {market.fully_qualified_symbol}."
|
||||
)
|
||||
return SpotPollingModelStateBuilder(
|
||||
open_orders_address,
|
||||
|
@ -350,7 +352,9 @@ def _websocket_model_state_builder_factory(
|
|||
context, websocket_manager, health_check, perp_market
|
||||
)
|
||||
else:
|
||||
raise Exception(f"Could not determine type of market {market.symbol}")
|
||||
raise Exception(
|
||||
f"Could not determine type of market {market.fully_qualified_symbol} - {market}"
|
||||
)
|
||||
|
||||
model_state = ModelState(
|
||||
order_owner,
|
||||
|
|
|
@ -60,6 +60,7 @@ from .orders import Order, OrderBook, OrderType, Side
|
|||
class MarketInstructionBuilder(metaclass=abc.ABCMeta):
|
||||
def __init__(self) -> None:
|
||||
self._logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
self.open_orders_address: typing.Optional[PublicKey] = None
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_cancel_order_instructions(
|
||||
|
@ -120,7 +121,7 @@ class MarketOperations(metaclass=abc.ABCMeta):
|
|||
|
||||
@property
|
||||
def symbol(self) -> str:
|
||||
return self.market.symbol
|
||||
return self.market.fully_qualified_symbol
|
||||
|
||||
@property
|
||||
def inventory_source(self) -> InventorySource:
|
||||
|
@ -269,7 +270,9 @@ class NullMarketOperations(MarketOperations):
|
|||
return []
|
||||
|
||||
def load_orderbook(self) -> OrderBook:
|
||||
return OrderBook(self.market.symbol, NullLotSizeConverter(), [], [])
|
||||
return OrderBook(
|
||||
self.market.fully_qualified_symbol, NullLotSizeConverter(), [], []
|
||||
)
|
||||
|
||||
def load_my_orders(self, include_expired: bool = False) -> typing.Sequence[Order]:
|
||||
return []
|
||||
|
@ -287,4 +290,4 @@ class NullMarketOperations(MarketOperations):
|
|||
return SYSTEM_PROGRAM_ADDRESS
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« NullMarketOperations [{self.market.symbol}] »"""
|
||||
return f"""« NullMarketOperations [{self.market.fully_qualified_symbol}] »"""
|
||||
|
|
|
@ -83,6 +83,13 @@ class Market(metaclass=abc.ABCMeta):
|
|||
def symbol(self) -> str:
|
||||
return f"{self.base.symbol}/{self.quote.symbol}"
|
||||
|
||||
@property
|
||||
@abc.abstractproperty
|
||||
def fully_qualified_symbol(self) -> str:
|
||||
raise NotImplementedError(
|
||||
"Market.fully_qualified_symbol is not implemented on the base type."
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« Market {self.symbol} »"
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ class ModelState:
|
|||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« ModelState for market '{self.market.symbol}'
|
||||
return f"""« ModelState for market '{self.market.fully_qualified_symbol}'
|
||||
Group: {self.group_watcher.latest.address}
|
||||
Account: {self.account_watcher.latest.address}
|
||||
Price: {self.price_watcher.latest}
|
||||
|
|
|
@ -23,7 +23,7 @@ from datetime import datetime
|
|||
from decimal import Decimal
|
||||
|
||||
from .context import Context
|
||||
from .markets import Market
|
||||
from .loadedmarket import LoadedMarket
|
||||
|
||||
|
||||
# # 🥭 Oracles
|
||||
|
@ -56,7 +56,7 @@ class OracleSource:
|
|||
provider_name: str,
|
||||
source_name: str,
|
||||
supports: SupportedOracleFeature,
|
||||
market: Market,
|
||||
market: LoadedMarket,
|
||||
) -> None:
|
||||
self.provider_name = provider_name
|
||||
self.source_name = source_name
|
||||
|
@ -64,7 +64,7 @@ class OracleSource:
|
|||
self.market = market
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« OracleSource '{self.source_name}' from '{self.provider_name}' for market '{self.market.symbol}' [{self.supports}] »"
|
||||
return f"« OracleSource '{self.source_name}' from '{self.provider_name}' for market '{self.market.fully_qualified_symbol}' [{self.supports}] »"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
||||
|
@ -79,7 +79,7 @@ class Price:
|
|||
self,
|
||||
source: OracleSource,
|
||||
timestamp: datetime,
|
||||
market: Market,
|
||||
market: LoadedMarket,
|
||||
top_bid: Decimal,
|
||||
mid_price: Decimal,
|
||||
top_ask: Decimal,
|
||||
|
@ -87,7 +87,7 @@ class Price:
|
|||
) -> None:
|
||||
self.source: OracleSource = source
|
||||
self.timestamp: datetime = timestamp
|
||||
self.market: Market = market
|
||||
self.market: LoadedMarket = market
|
||||
self.top_bid: Decimal = top_bid
|
||||
self.mid_price: Decimal = mid_price
|
||||
self.top_ask: Decimal = top_ask
|
||||
|
@ -101,7 +101,7 @@ class Price:
|
|||
confidence = ""
|
||||
if self.source.supports & SupportedOracleFeature.CONFIDENCE:
|
||||
confidence = f" +/- {self.confidence:,.8f}"
|
||||
return f"« Price [{self.source.provider_name}] {self.market.symbol} at {self.timestamp}: {self.mid_price:,.8f}{confidence} »"
|
||||
return f"« Price [{self.source.provider_name}] {self.market.fully_qualified_symbol} at {self.timestamp}: {self.mid_price:,.8f}{confidence} »"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
||||
|
@ -112,7 +112,7 @@ class Price:
|
|||
# Derived versions of this class can fetch prices for a specific market.
|
||||
#
|
||||
class Oracle(metaclass=abc.ABCMeta):
|
||||
def __init__(self, name: str, market: Market) -> None:
|
||||
def __init__(self, name: str, market: LoadedMarket) -> None:
|
||||
self._logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
self.name = name
|
||||
self.market = market
|
||||
|
@ -136,7 +136,7 @@ class Oracle(metaclass=abc.ABCMeta):
|
|||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« Oracle {self.name} [{self.market.symbol}] »"
|
||||
return f"« Oracle {self.name} [{self.market.fully_qualified_symbol}] »"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
||||
|
@ -152,7 +152,7 @@ class OracleProvider(metaclass=abc.ABCMeta):
|
|||
|
||||
@abc.abstractmethod
|
||||
def oracle_for_market(
|
||||
self, context: Context, market: Market
|
||||
self, context: Context, market: LoadedMarket
|
||||
) -> typing.Optional[Oracle]:
|
||||
raise NotImplementedError(
|
||||
"OracleProvider.create_oracle_for_market() is not implemented on the base type."
|
||||
|
|
|
@ -25,7 +25,7 @@ from rx.subject.subject import Subject
|
|||
|
||||
from ...context import Context
|
||||
from ...datetimes import utc_now, datetime_from_timestamp
|
||||
from ...markets import Market
|
||||
from ...loadedmarket import LoadedMarket
|
||||
from ...observables import Disposable, DisposeWrapper
|
||||
from ...oracle import (
|
||||
Oracle,
|
||||
|
@ -61,10 +61,10 @@ FtxOracleConfidence: Decimal = Decimal(0)
|
|||
# Implements the `Oracle` abstract base class specialised to the Ftx Network.
|
||||
#
|
||||
class FtxOracle(Oracle):
|
||||
def __init__(self, market: Market, ftx_symbol: str) -> None:
|
||||
name = f"Ftx Oracle for {market.symbol} / {ftx_symbol}"
|
||||
def __init__(self, market: LoadedMarket, ftx_symbol: str) -> None:
|
||||
name = f"Ftx Oracle for {market.fully_qualified_symbol} / {ftx_symbol}"
|
||||
super().__init__(name, market)
|
||||
self.market: Market = market
|
||||
self.market: LoadedMarket = market
|
||||
self.ftx_symbol: str = ftx_symbol
|
||||
features: SupportedOracleFeature = (
|
||||
SupportedOracleFeature.MID_PRICE | SupportedOracleFeature.TOP_BID_AND_OFFER
|
||||
|
@ -146,7 +146,7 @@ class FtxOracleProvider(OracleProvider):
|
|||
super().__init__("Ftx Oracle Factory")
|
||||
|
||||
def oracle_for_market(
|
||||
self, context: Context, market: Market
|
||||
self, context: Context, market: LoadedMarket
|
||||
) -> typing.Optional[Oracle]:
|
||||
symbol = self._market_symbol_to_ftx_symbol(market.symbol)
|
||||
return FtxOracle(market, symbol)
|
||||
|
|
|
@ -23,7 +23,6 @@ from decimal import Decimal
|
|||
from ...context import Context
|
||||
from ...datetimes import utc_now
|
||||
from ...loadedmarket import LoadedMarket
|
||||
from ...markets import Market
|
||||
from ...observables import observable_pipeline_error_reporter
|
||||
from ...oracle import (
|
||||
Oracle,
|
||||
|
@ -33,7 +32,6 @@ from ...oracle import (
|
|||
SupportedOracleFeature,
|
||||
)
|
||||
from ...orders import OrderBook
|
||||
from ...porcelain import market as porcelain_market
|
||||
|
||||
|
||||
# # 🥭 Market
|
||||
|
@ -58,7 +56,7 @@ MarketOracleConfidence: Decimal = Decimal(0)
|
|||
#
|
||||
class MarketOracle(Oracle):
|
||||
def __init__(self, market: LoadedMarket):
|
||||
name = f"Market Oracle for {market.symbol}"
|
||||
name = f"Market Oracle for {market.fully_qualified_symbol}"
|
||||
super().__init__(name, market)
|
||||
self.loaded_market: LoadedMarket = market
|
||||
features: SupportedOracleFeature = SupportedOracleFeature.TOP_BID_AND_OFFER
|
||||
|
@ -116,10 +114,9 @@ class MarketOracleProvider(OracleProvider):
|
|||
super().__init__("Market Oracle Factory")
|
||||
|
||||
def oracle_for_market(
|
||||
self, context: Context, market: Market
|
||||
self, context: Context, market: LoadedMarket
|
||||
) -> typing.Optional[Oracle]:
|
||||
loaded_market: LoadedMarket = porcelain_market(context, market.symbol)
|
||||
return MarketOracle(loaded_market)
|
||||
return MarketOracle(market)
|
||||
|
||||
def all_available_symbols(self, context: Context) -> typing.Sequence[str]:
|
||||
all_markets = context.market_lookup.all_markets()
|
||||
|
|
|
@ -25,7 +25,7 @@ from solana.publickey import PublicKey
|
|||
from ...accountinfo import AccountInfo
|
||||
from ...context import Context
|
||||
from ...datetimes import utc_now
|
||||
from ...markets import Market
|
||||
from ...loadedmarket import LoadedMarket
|
||||
from ...observables import observable_pipeline_error_reporter
|
||||
from ...oracle import (
|
||||
Oracle,
|
||||
|
@ -74,11 +74,13 @@ from .layouts import (
|
|||
|
||||
|
||||
class PythOracle(Oracle):
|
||||
def __init__(self, context: Context, market: Market, product_data: typing.Any):
|
||||
name = f"Pyth Oracle for {market.symbol}"
|
||||
def __init__(
|
||||
self, context: Context, market: LoadedMarket, product_data: typing.Any
|
||||
):
|
||||
name = f"Pyth Oracle for {market.fully_qualified_symbol}"
|
||||
super().__init__(name, market)
|
||||
self.context: Context = context
|
||||
self.market: Market = market
|
||||
self.market: LoadedMarket = market
|
||||
self.product_data: typing.Any = product_data
|
||||
self.address: PublicKey = product_data.address
|
||||
features: SupportedOracleFeature = (
|
||||
|
@ -153,7 +155,9 @@ class PythOracleProvider(OracleProvider):
|
|||
super().__init__(f"Pyth Oracle Factory [{self.address}]")
|
||||
self.context: Context = context
|
||||
|
||||
def oracle_for_market(self, _: Context, market: Market) -> typing.Optional[Oracle]:
|
||||
def oracle_for_market(
|
||||
self, _: Context, market: LoadedMarket
|
||||
) -> typing.Optional[Oracle]:
|
||||
pyth_symbol = self._market_symbol_to_pyth_symbol(market.symbol)
|
||||
products = self._fetch_all_pyth_products(self.context, self.address)
|
||||
for product in products:
|
||||
|
|
|
@ -25,7 +25,6 @@ from ...cache import Cache
|
|||
from ...context import Context
|
||||
from ...datetimes import utc_now
|
||||
from ...loadedmarket import LoadedMarket
|
||||
from ...markets import Market
|
||||
from ...observables import observable_pipeline_error_reporter
|
||||
from ...oracle import (
|
||||
Oracle,
|
||||
|
@ -35,7 +34,6 @@ from ...oracle import (
|
|||
SupportedOracleFeature,
|
||||
)
|
||||
from ...perpmarket import PerpMarket
|
||||
from ...porcelain import market as porcelain_market
|
||||
from ...spotmarket import SpotMarket
|
||||
|
||||
|
||||
|
@ -60,8 +58,10 @@ StubOracleConfidence: Decimal = Decimal(0)
|
|||
|
||||
|
||||
class StubOracle(Oracle):
|
||||
def __init__(self, market: Market, index: int, cache_address: PublicKey) -> None:
|
||||
name = f"Stub Oracle for {market.symbol}"
|
||||
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
|
||||
|
@ -116,19 +116,18 @@ class StubOracleProvider(OracleProvider):
|
|||
super().__init__("Stub Oracle Factory")
|
||||
|
||||
def oracle_for_market(
|
||||
self, context: Context, market: Market
|
||||
self, context: Context, market: LoadedMarket
|
||||
) -> typing.Optional[Oracle]:
|
||||
loaded_market: LoadedMarket = porcelain_market(context, market.symbol)
|
||||
if SpotMarket.isa(loaded_market):
|
||||
spot_market = SpotMarket.ensure(loaded_market)
|
||||
if SpotMarket.isa(market):
|
||||
spot_market = SpotMarket.ensure(market)
|
||||
spot_index: int = spot_market.group.slot_by_spot_market_address(
|
||||
loaded_market.address
|
||||
market.address
|
||||
).index
|
||||
return StubOracle(spot_market, spot_index, spot_market.group.cache)
|
||||
elif PerpMarket.isa(loaded_market):
|
||||
perp_market = PerpMarket.ensure(loaded_market)
|
||||
elif PerpMarket.isa(market):
|
||||
perp_market = PerpMarket.ensure(market)
|
||||
perp_index: int = perp_market.group.slot_by_perp_market_address(
|
||||
loaded_market.address
|
||||
market.address
|
||||
).index
|
||||
return StubOracle(perp_market, perp_index, perp_market.group.cache)
|
||||
|
||||
|
|
|
@ -158,13 +158,19 @@ class PerpMarket(LoadedMarket):
|
|||
@staticmethod
|
||||
def ensure(market: Market) -> "PerpMarket":
|
||||
if not PerpMarket.isa(market):
|
||||
raise Exception(f"Market for {market.symbol} is not a Perp market")
|
||||
raise Exception(
|
||||
f"Market for {market.fully_qualified_symbol} is not a Perp market"
|
||||
)
|
||||
return typing.cast(PerpMarket, market)
|
||||
|
||||
@property
|
||||
def symbol(self) -> str:
|
||||
return f"{self.base.symbol}-PERP"
|
||||
|
||||
@property
|
||||
def fully_qualified_symbol(self) -> str:
|
||||
return f"perp:{self.symbol}"
|
||||
|
||||
@property
|
||||
def group(self) -> Group:
|
||||
return self.underlying_perp_market.group
|
||||
|
@ -286,10 +292,6 @@ class PerpMarketInstructionBuilder(MarketInstructionBuilder):
|
|||
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_perp_cancel_order_instructions(
|
||||
self.context,
|
||||
self.wallet,
|
||||
|
@ -300,10 +302,6 @@ class PerpMarketInstructionBuilder(MarketInstructionBuilder):
|
|||
)
|
||||
|
||||
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_perp_place_order_instructions(
|
||||
self.context,
|
||||
self.wallet,
|
||||
|
@ -327,11 +325,6 @@ class PerpMarketInstructionBuilder(MarketInstructionBuilder):
|
|||
def build_crank_instructions(
|
||||
self, 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."
|
||||
)
|
||||
|
||||
distinct_addresses: typing.List[PublicKey] = [self.account.address]
|
||||
for address in addresses:
|
||||
if address not in distinct_addresses:
|
||||
|
@ -371,10 +364,6 @@ class PerpMarketInstructionBuilder(MarketInstructionBuilder):
|
|||
def build_cancel_all_orders_instructions(
|
||||
self, 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_perp_cancel_all_orders_instructions(
|
||||
self.context,
|
||||
self.wallet,
|
||||
|
@ -419,14 +408,10 @@ class PerpMarketOperations(MarketOperations):
|
|||
def perp_market(self) -> PerpMarket:
|
||||
return self.market_instruction_builder.perp_market
|
||||
|
||||
@property
|
||||
def market_name(self) -> str:
|
||||
return self.perp_market.symbol
|
||||
|
||||
def cancel_order(
|
||||
self, order: Order, ok_if_missing: bool = False
|
||||
) -> typing.Sequence[str]:
|
||||
self._logger.info(f"Cancelling {self.market_name} order {order}.")
|
||||
self._logger.info(f"Cancelling {self.symbol} order {order}.")
|
||||
signers: CombinableInstructions = CombinableInstructions.from_wallet(
|
||||
self.wallet
|
||||
)
|
||||
|
@ -447,7 +432,7 @@ class PerpMarketOperations(MarketOperations):
|
|||
self.wallet
|
||||
)
|
||||
order_with_client_id: Order = order.with_update(client_id=client_id)
|
||||
self._logger.info(f"Placing {self.market_name} order {order_with_client_id}.")
|
||||
self._logger.info(f"Placing {self.symbol} order {order_with_client_id}.")
|
||||
place: CombinableInstructions = (
|
||||
self.market_instruction_builder.build_place_order_instructions(
|
||||
order_with_client_id
|
||||
|
@ -507,7 +492,7 @@ class PerpMarketOperations(MarketOperations):
|
|||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« PerpMarketOperations [{self.market_name}] »"""
|
||||
return f"""« PerpMarketOperations [{self.symbol}] »"""
|
||||
|
||||
|
||||
# # 🥭 PerpMarketStub class
|
||||
|
@ -534,6 +519,10 @@ class PerpMarketStub(Market):
|
|||
)
|
||||
self.group_address: PublicKey = group_address
|
||||
|
||||
@property
|
||||
def fully_qualified_symbol(self) -> str:
|
||||
return f"perp:{self.symbol}"
|
||||
|
||||
def load(
|
||||
self, context: Context, group: typing.Optional[Group] = None
|
||||
) -> PerpMarket:
|
||||
|
|
|
@ -120,7 +120,7 @@ def instruction_builder(
|
|||
) -> MarketInstructionBuilder:
|
||||
loaded_market: LoadedMarket = market(context, symbol)
|
||||
if dry_run:
|
||||
return NullMarketInstructionBuilder(loaded_market.symbol)
|
||||
return NullMarketInstructionBuilder(loaded_market.fully_qualified_symbol)
|
||||
|
||||
if SerumMarket.isa(loaded_market):
|
||||
return SerumMarketInstructionBuilder.load(
|
||||
|
|
|
@ -89,9 +89,15 @@ class SerumMarket(LoadedMarket):
|
|||
@staticmethod
|
||||
def ensure(market: Market) -> "SerumMarket":
|
||||
if not SerumMarket.isa(market):
|
||||
raise Exception(f"Market for {market.symbol} is not a Serum market")
|
||||
raise Exception(
|
||||
f"Market for {market.fully_qualified_symbol} is not a Serum market"
|
||||
)
|
||||
return typing.cast(SerumMarket, market)
|
||||
|
||||
@property
|
||||
def fully_qualified_symbol(self) -> str:
|
||||
return f"serum:{self.symbol}"
|
||||
|
||||
@property
|
||||
def bids_address(self) -> PublicKey:
|
||||
return self.underlying_serum_market.state.bids()
|
||||
|
@ -426,7 +432,9 @@ class SerumMarketOperations(MarketOperations):
|
|||
def cancel_order(
|
||||
self, order: Order, ok_if_missing: bool = False
|
||||
) -> typing.Sequence[str]:
|
||||
self._logger.info(f"Cancelling {self.serum_market.symbol} order {order}.")
|
||||
self._logger.info(
|
||||
f"Cancelling {self.serum_market.fully_qualified_symbol} order {order}."
|
||||
)
|
||||
signers: CombinableInstructions = CombinableInstructions.from_wallet(
|
||||
self.wallet
|
||||
)
|
||||
|
@ -463,7 +471,7 @@ class SerumMarketOperations(MarketOperations):
|
|||
order_type=order.order_type,
|
||||
)
|
||||
self._logger.info(
|
||||
f"Placing {self.serum_market.symbol} order {order_with_client_id}."
|
||||
f"Placing {self.serum_market.fully_qualified_symbol} order {order_with_client_id}."
|
||||
)
|
||||
place: CombinableInstructions = (
|
||||
self.market_instruction_builder.build_place_order_instructions(
|
||||
|
@ -546,7 +554,7 @@ class SerumMarketOperations(MarketOperations):
|
|||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« SerumMarketOperations [{self.serum_market.symbol}] »"""
|
||||
return f"""« SerumMarketOperations [{self.serum_market.fully_qualified_symbol}] »"""
|
||||
|
||||
|
||||
# # 🥭 SerumMarketStub class
|
||||
|
@ -573,6 +581,10 @@ class SerumMarketStub(Market):
|
|||
self.base: Token = base
|
||||
self.quote: Token = quote
|
||||
|
||||
@property
|
||||
def fully_qualified_symbol(self) -> str:
|
||||
return f"serum:{self.symbol}"
|
||||
|
||||
def load(self, context: Context) -> SerumMarket:
|
||||
underlying_serum_market: PySerumMarket = PySerumMarket.load(
|
||||
context.client.compatible_client,
|
||||
|
|
|
@ -252,7 +252,7 @@ class SimpleMarketMaker:
|
|||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« SimpleMarketMaker for market '{self.market.symbol}' »"""
|
||||
return f"""« SimpleMarketMaker for market '{self.market.fully_qualified_symbol}' »"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
||||
|
|
|
@ -93,9 +93,15 @@ class SpotMarket(LoadedMarket):
|
|||
@staticmethod
|
||||
def ensure(market: Market) -> "SpotMarket":
|
||||
if not SpotMarket.isa(market):
|
||||
raise Exception(f"Market for {market.symbol} is not a Spot market")
|
||||
raise Exception(
|
||||
f"Market for {market.fully_qualified_symbol} is not a Spot market"
|
||||
)
|
||||
return typing.cast(SpotMarket, market)
|
||||
|
||||
@property
|
||||
def fully_qualified_symbol(self) -> str:
|
||||
return f"spot:{self.symbol}"
|
||||
|
||||
@property
|
||||
def bids_address(self) -> PublicKey:
|
||||
return self.underlying_serum_market.state.bids()
|
||||
|
@ -410,7 +416,7 @@ class SpotMarketInstructionBuilder(MarketInstructionBuilder):
|
|||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« SpotMarketInstructionBuilder [{self.spot_market.symbol}] »"
|
||||
return f"« SpotMarketInstructionBuilder [{self.spot_market.fully_qualified_symbol}] »"
|
||||
|
||||
|
||||
# # 🥭 SpotMarketOperations class
|
||||
|
@ -459,7 +465,9 @@ class SpotMarketOperations(MarketOperations):
|
|||
def cancel_order(
|
||||
self, order: Order, ok_if_missing: bool = False
|
||||
) -> typing.Sequence[str]:
|
||||
self._logger.info(f"Cancelling {self.spot_market.symbol} order {order}.")
|
||||
self._logger.info(
|
||||
f"Cancelling {self.spot_market.fully_qualified_symbol} order {order}."
|
||||
)
|
||||
signers: CombinableInstructions = CombinableInstructions.from_wallet(
|
||||
self.wallet
|
||||
)
|
||||
|
@ -486,7 +494,9 @@ class SpotMarketOperations(MarketOperations):
|
|||
order_with_client_id: Order = order.with_update(
|
||||
client_id=client_id
|
||||
).with_update(owner=self.open_orders_address or SYSTEM_PROGRAM_ADDRESS)
|
||||
self._logger.info(f"Placing {self.spot_market.symbol} order {order}.")
|
||||
self._logger.info(
|
||||
f"Placing {self.spot_market.fully_qualified_symbol} order {order}."
|
||||
)
|
||||
place: CombinableInstructions = (
|
||||
self.market_instruction_builder.build_place_order_instructions(
|
||||
order_with_client_id
|
||||
|
@ -577,7 +587,7 @@ class SpotMarketOperations(MarketOperations):
|
|||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« SpotMarketOperations [{self.spot_market.symbol}] »"
|
||||
return f"« SpotMarketOperations [{self.spot_market.fully_qualified_symbol}] »"
|
||||
|
||||
|
||||
# # 🥭 SpotMarketStub class
|
||||
|
@ -606,6 +616,10 @@ class SpotMarketStub(Market):
|
|||
self.quote: Token = quote
|
||||
self.group_address: PublicKey = group_address
|
||||
|
||||
@property
|
||||
def fully_qualified_symbol(self) -> str:
|
||||
return f"spot:{self.symbol}"
|
||||
|
||||
def load(self, context: Context, group: typing.Optional[Group]) -> SpotMarket:
|
||||
actual_group: Group = group or Group.load(context, self.group_address)
|
||||
underlying_serum_market: PySerumMarket = PySerumMarket.load(
|
||||
|
|
|
@ -31,7 +31,7 @@ from .instructions import build_serum_create_openorders_instructions
|
|||
from .instrumentvalue import InstrumentValue
|
||||
from .inventory import Inventory
|
||||
from .loadedmarket import LoadedMarket
|
||||
from .markets import Market, InventorySource
|
||||
from .markets import InventorySource
|
||||
from .modelstate import EventQueue
|
||||
from .observables import Disposable, LatestItemObserverSubscriber
|
||||
from .openorders import OpenOrders
|
||||
|
@ -137,7 +137,7 @@ def build_spot_open_orders_watcher(
|
|||
)
|
||||
open_orders_address = market_operations.create_openorders()
|
||||
logging.info(
|
||||
f"Created {spot_market.symbol} OpenOrders at: {open_orders_address}"
|
||||
f"Created {spot_market.fully_qualified_symbol} OpenOrders at: {open_orders_address}"
|
||||
)
|
||||
|
||||
spot_open_orders_subscription = WebSocketAccountSubscription[OpenOrders](
|
||||
|
@ -193,7 +193,7 @@ def build_serum_open_orders_watcher(
|
|||
open_orders_address = create_open_orders.signers[0].public_key
|
||||
|
||||
logging.info(
|
||||
f"Creating OpenOrders account for market {serum_market.symbol} at {open_orders_address}."
|
||||
f"Creating OpenOrders account for market {serum_market.fully_qualified_symbol} at {open_orders_address}."
|
||||
)
|
||||
signers: CombinableInstructions = CombinableInstructions.from_wallet(wallet)
|
||||
transaction_ids = (signers + create_open_orders).execute(context)
|
||||
|
@ -262,13 +262,13 @@ def build_price_watcher(
|
|||
health_check: HealthCheck,
|
||||
disposer: Disposable,
|
||||
provider_name: str,
|
||||
market: Market,
|
||||
market: LoadedMarket,
|
||||
) -> LatestItemObserverSubscriber[Price]:
|
||||
oracle_provider: OracleProvider = create_oracle_provider(context, provider_name)
|
||||
oracle = oracle_provider.oracle_for_market(context, market)
|
||||
if oracle is None:
|
||||
raise Exception(
|
||||
f"Could not find oracle for market {market.symbol} from provider {provider_name}."
|
||||
f"Could not find oracle for market {market.fully_qualified_symbol} from provider {provider_name}."
|
||||
)
|
||||
|
||||
initial_price = oracle.fetch_price(context)
|
||||
|
@ -373,7 +373,7 @@ def build_orderbook_watcher(
|
|||
or orderbook_infos[1] is None
|
||||
):
|
||||
raise Exception(
|
||||
f"Could not find {market.symbol} order book at addresses {orderbook_addresses}."
|
||||
f"Could not find {market.fully_qualified_symbol} order book at addresses {orderbook_addresses}."
|
||||
)
|
||||
|
||||
initial_orderbook: OrderBook = market.parse_account_infos_to_orderbook(
|
||||
|
|
|
@ -182,6 +182,10 @@ def fake_loaded_market(
|
|||
base_lot_size: Decimal = Decimal(1), quote_lot_size: Decimal = Decimal(1)
|
||||
) -> mango.LoadedMarket:
|
||||
class FakeLoadedMarket(mango.LoadedMarket):
|
||||
@property
|
||||
def fully_qualified_symbol(self) -> str:
|
||||
return "full:MARKET/SYMBOL"
|
||||
|
||||
@property
|
||||
def bids_address(self) -> PublicKey:
|
||||
return fake_seeded_public_key("bids_address")
|
||||
|
@ -270,7 +274,7 @@ def fake_order_id(index: int, price: int) -> int:
|
|||
|
||||
|
||||
def fake_price(
|
||||
market: mango.Market = fake_loaded_market(),
|
||||
market: mango.LoadedMarket = fake_loaded_market(),
|
||||
price: Decimal = Decimal(100),
|
||||
bid: Decimal = Decimal(99),
|
||||
ask: Decimal = Decimal(101),
|
||||
|
|
|
@ -10,7 +10,9 @@ def test_market_symbol_matching() -> None:
|
|||
assert mango.Market.symbols_match("eth/usdc", "eth/usdc")
|
||||
assert mango.Market.symbols_match("btc/usdc", "BTC/USDC")
|
||||
assert mango.Market.symbols_match("ETH/USDC", "eth/usdc")
|
||||
assert mango.Market.symbols_match("serum:ETH/USDC", "serum:eth/usdc")
|
||||
assert not mango.Market.symbols_match("ETH/USDC", "BTC/USDC")
|
||||
assert not mango.Market.symbols_match("serum:ETH/USDC", "spot:eth/usdc")
|
||||
|
||||
|
||||
def test_serum_market_lookup() -> None:
|
||||
|
|
Loading…
Reference in New Issue