2021-06-07 07:10:18 -07:00
|
|
|
|
# # ⚠ Warning
|
|
|
|
|
#
|
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
|
|
|
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
|
|
|
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
|
|
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
|
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
#
|
|
|
|
|
# [🥭 Mango Markets](https://mango.markets/) support is available at:
|
|
|
|
|
# [Docs](https://docs.mango.markets/)
|
|
|
|
|
# [Discord](https://discord.gg/67jySBhxrg)
|
|
|
|
|
# [Twitter](https://twitter.com/mangomarkets)
|
|
|
|
|
# [Github](https://github.com/blockworks-foundation)
|
|
|
|
|
# [Email](mailto:hello@blockworks.foundation)
|
|
|
|
|
|
2021-09-07 13:44:48 -07:00
|
|
|
|
import datetime
|
2021-06-07 07:10:18 -07:00
|
|
|
|
import logging
|
2021-06-10 13:28:29 -07:00
|
|
|
|
import multiprocessing
|
2021-11-15 12:39:29 -08:00
|
|
|
|
import requests
|
2021-09-07 11:07:22 -07:00
|
|
|
|
import time
|
2021-06-07 07:10:18 -07:00
|
|
|
|
import typing
|
|
|
|
|
|
|
|
|
|
from decimal import Decimal
|
2021-11-09 05:23:36 -08:00
|
|
|
|
from rx.scheduler.threadpoolscheduler import ThreadPoolScheduler
|
2021-06-07 07:10:18 -07:00
|
|
|
|
from solana.publickey import PublicKey
|
2021-06-08 16:47:15 -07:00
|
|
|
|
from solana.rpc.commitment import Commitment
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
2021-08-26 07:48:25 -07:00
|
|
|
|
from .client import BetterClient
|
|
|
|
|
from .constants import MangoConstants
|
2021-08-19 01:46:54 -07:00
|
|
|
|
from .instructionreporter import InstructionReporter, CompoundInstructionReporter
|
2021-11-08 03:39:09 -08:00
|
|
|
|
from .instrumentlookup import InstrumentLookup
|
2021-07-23 02:20:44 -07:00
|
|
|
|
from .marketlookup import MarketLookup
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
|
|
|
2021-06-07 11:38:43 -07:00
|
|
|
|
# # 🥭 Context class
|
|
|
|
|
#
|
|
|
|
|
# A `Context` object to manage Solana connection and Mango configuration.
|
|
|
|
|
#
|
2021-06-07 07:10:18 -07:00
|
|
|
|
class Context:
|
2021-09-16 07:36:52 -07:00
|
|
|
|
def __init__(self, name: str, cluster_name: str, cluster_url: str, skip_preflight: bool, commitment: str,
|
|
|
|
|
blockhash_commitment: str, encoding: str, blockhash_cache_duration: datetime.timedelta,
|
|
|
|
|
mango_program_address: PublicKey, serum_program_address: PublicKey, group_name: str,
|
|
|
|
|
group_address: PublicKey, gma_chunk_size: Decimal, gma_chunk_pause: Decimal,
|
2021-11-09 05:23:36 -08:00
|
|
|
|
instrument_lookup: InstrumentLookup, market_lookup: MarketLookup) -> None:
|
2021-06-07 07:10:18 -07:00
|
|
|
|
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
2021-08-09 02:27:47 -07:00
|
|
|
|
self.name: str = name
|
2021-08-26 02:31:02 -07:00
|
|
|
|
instruction_reporter: InstructionReporter = CompoundInstructionReporter.from_addresses(
|
|
|
|
|
mango_program_address, serum_program_address)
|
2021-08-26 07:48:25 -07:00
|
|
|
|
self.client: BetterClient = BetterClient.from_configuration(
|
2021-09-16 07:36:52 -07:00
|
|
|
|
name, cluster_name, cluster_url, Commitment(commitment), Commitment(blockhash_commitment), skip_preflight, encoding, blockhash_cache_duration, instruction_reporter)
|
2021-08-26 02:31:02 -07:00
|
|
|
|
self.mango_program_address: PublicKey = mango_program_address
|
|
|
|
|
self.serum_program_address: PublicKey = serum_program_address
|
2021-06-07 07:10:18 -07:00
|
|
|
|
self.group_name: str = group_name
|
2021-08-26 02:31:02 -07:00
|
|
|
|
self.group_address: PublicKey = group_address
|
2021-09-13 04:17:19 -07:00
|
|
|
|
self.gma_chunk_size: Decimal = gma_chunk_size
|
|
|
|
|
self.gma_chunk_pause: Decimal = gma_chunk_pause
|
2021-11-08 03:39:09 -08:00
|
|
|
|
self.instrument_lookup: InstrumentLookup = instrument_lookup
|
2021-07-23 02:20:44 -07:00
|
|
|
|
self.market_lookup: MarketLookup = market_lookup
|
2021-06-18 05:50:33 -07:00
|
|
|
|
|
2021-08-21 08:03:13 -07:00
|
|
|
|
self.ping_interval: int = 10
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
2021-09-07 11:07:22 -07:00
|
|
|
|
self._last_generated_client_id: int = 0
|
|
|
|
|
|
2021-06-07 07:10:18 -07:00
|
|
|
|
# kangda said in Discord: https://discord.com/channels/791995070613159966/836239696467591186/847816026245693451
|
|
|
|
|
# "I think you are better off doing 4,8,16,20,30"
|
2021-06-25 07:50:37 -07:00
|
|
|
|
self.retry_pauses: typing.Sequence[Decimal] = [Decimal(4), Decimal(
|
2021-06-07 07:10:18 -07:00
|
|
|
|
8), Decimal(16), Decimal(20), Decimal(30)]
|
|
|
|
|
|
2021-09-02 00:30:06 -07:00
|
|
|
|
def create_thread_pool_scheduler(self) -> ThreadPoolScheduler:
|
|
|
|
|
return ThreadPoolScheduler(multiprocessing.cpu_count())
|
2021-06-10 13:28:29 -07:00
|
|
|
|
|
2021-09-07 11:07:22 -07:00
|
|
|
|
def generate_client_id(self) -> int:
|
|
|
|
|
# Previously used a random client ID strategy, which may be appropriate for some people.
|
|
|
|
|
# 9223372036854775807 is sys.maxsize for 64-bit systems, with a bit_length of 63.
|
|
|
|
|
# We explicitly want to use a max of 64-bits though, so we use the number instead of
|
|
|
|
|
# sys.maxsize, which could be lower on 32-bit systems or higher on 128-bit systems.
|
|
|
|
|
# return random.randrange(9223372036854775807)
|
|
|
|
|
#
|
|
|
|
|
# After this discussion with Max on Discord (https://discord.com/channels/791995070613159966/818978757648842782/884751007656054804):
|
|
|
|
|
# can you generate monotonic ids?
|
|
|
|
|
# in case not the result wouldn't be different from what we have rn, which is random display
|
|
|
|
|
# so there's still a net benefit for changing the UI
|
|
|
|
|
# and if you could use the same id generation scheme (unix time in ms) it would even work well with the UI :slight_smile:
|
|
|
|
|
#
|
|
|
|
|
# We go with the time in milliseconds. We get the time in nanoseconds and divide it by 1,000,000 to get
|
|
|
|
|
# the time in milliseconds.
|
|
|
|
|
#
|
|
|
|
|
# But there's more! Because this can be called in a burst, for, say, a dozen orders all within the same
|
|
|
|
|
# millisecond. And using duplicate client order IDs would be Bad. So we keep track of the last one we
|
|
|
|
|
# sent, and we just add one if we get an identical value.
|
|
|
|
|
new_id: int = round(time.time_ns() / 1000000)
|
|
|
|
|
if new_id <= self._last_generated_client_id:
|
|
|
|
|
new_id = self._last_generated_client_id + 1
|
|
|
|
|
self._last_generated_client_id = new_id
|
|
|
|
|
return new_id
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
|
|
def lookup_group_name(self, group_address: PublicKey) -> str:
|
2021-06-28 01:48:16 -07:00
|
|
|
|
group_address_str = str(group_address)
|
|
|
|
|
for group in MangoConstants["groups"]:
|
2021-08-26 02:31:02 -07:00
|
|
|
|
if group["cluster"] == self.client.cluster_name and group["publicKey"] == group_address_str:
|
2021-11-09 05:23:36 -08:00
|
|
|
|
return str(group["name"])
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
2021-06-28 01:48:16 -07:00
|
|
|
|
return "« Unknown Group »"
|
2021-06-07 11:38:43 -07:00
|
|
|
|
|
2021-11-15 12:39:29 -08:00
|
|
|
|
def fetch_stats(self, url_suffix: str) -> typing.Sequence[typing.Any]:
|
|
|
|
|
stats_url = f"https://mango-stats-v3.herokuapp.com/{url_suffix}"
|
|
|
|
|
stats_response = requests.get(stats_url)
|
|
|
|
|
return typing.cast(typing.Sequence[typing.Any], stats_response.json())
|
|
|
|
|
|
2021-06-07 07:10:18 -07:00
|
|
|
|
def __str__(self) -> str:
|
2021-08-09 02:27:47 -07:00
|
|
|
|
return f"""« 𝙲𝚘𝚗𝚝𝚎𝚡𝚝 '{self.name}':
|
2021-08-26 02:31:02 -07:00
|
|
|
|
Cluster Name: {self.client.cluster_name}
|
2021-08-07 07:07:19 -07:00
|
|
|
|
Cluster URL: {self.client.cluster_url}
|
2021-06-07 07:10:18 -07:00
|
|
|
|
Group Name: {self.group_name}
|
2021-08-26 02:31:02 -07:00
|
|
|
|
Group Address: {self.group_address}
|
|
|
|
|
Mango Program Address: {self.mango_program_address}
|
|
|
|
|
Serum Program Address: {self.serum_program_address}
|
2021-06-07 07:10:18 -07:00
|
|
|
|
»"""
|
|
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
|
return f"{self}"
|