OrderBook can now provide expired orders, if requested.
This commit is contained in:
parent
c3f7ad60f6
commit
906b419fc6
|
@ -60,7 +60,7 @@ market = mango.ensure_market_loaded(context, stub)
|
||||||
market_operations = mango.create_market_operations(context, wallet, account, market, dry_run=False)
|
market_operations = mango.create_market_operations(context, wallet, account, market, dry_run=False)
|
||||||
|
|
||||||
print("Initial order book:\n\t", market_operations.load_orderbook())
|
print("Initial order book:\n\t", market_operations.load_orderbook())
|
||||||
print("Your current orders:\n\t", market_operations.load_my_orders())
|
print("Your current orders:\n\t", market_operations.load_my_orders(include_expired=True))
|
||||||
|
|
||||||
# Go on - try to buy 1 SOL-PERP contract for $10.
|
# Go on - try to buy 1 SOL-PERP contract for $10.
|
||||||
order = mango.Order.from_basic_info(side=mango.Side.BUY,
|
order = mango.Order.from_basic_info(side=mango.Side.BUY,
|
||||||
|
@ -74,7 +74,7 @@ print("\n\nSleeping for 10 seconds...")
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
print("\n\nOrder book (including our new order):\n", market_operations.load_orderbook())
|
print("\n\nOrder book (including our new order):\n", market_operations.load_orderbook())
|
||||||
print("Your current orders:\n\t", market_operations.load_my_orders())
|
print("Your current orders:\n\t", market_operations.load_my_orders(include_expired=True))
|
||||||
|
|
||||||
cancellation_signatures = market_operations.cancel_order(placed_order)
|
cancellation_signatures = market_operations.cancel_order(placed_order)
|
||||||
print("\n\nCancellation signature:\n\t", cancellation_signatures)
|
print("\n\nCancellation signature:\n\t", cancellation_signatures)
|
||||||
|
@ -83,7 +83,7 @@ print("\n\nSleeping for 10 seconds...")
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
print("\n\nOrder book (without our order):\n", market_operations.load_orderbook())
|
print("\n\nOrder book (without our order):\n", market_operations.load_orderbook())
|
||||||
print("Your current orders:\n\t", market_operations.load_my_orders())
|
print("Your current orders:\n\t", market_operations.load_my_orders(include_expired=True))
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ if market is None:
|
||||||
market_operations = mango.create_market_operations(
|
market_operations = mango.create_market_operations(
|
||||||
context, wallet, account, market, args.dry_run
|
context, wallet, account, market, args.dry_run
|
||||||
)
|
)
|
||||||
orders = market_operations.load_my_orders()
|
orders = market_operations.load_my_orders(include_expired=True)
|
||||||
if len(orders) == 0:
|
if len(orders) == 0:
|
||||||
mango.output(f"No open orders on {market.symbol}")
|
mango.output(f"No open orders on {market.symbol}")
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -55,7 +55,7 @@ for account in accounts:
|
||||||
filename = f"trade-history-{account.address}.csv"
|
filename = f"trade-history-{account.address}.csv"
|
||||||
history: mango.TradeHistory = mango.TradeHistory()
|
history: mango.TradeHistory = mango.TradeHistory()
|
||||||
if args.most_recent_hours:
|
if args.most_recent_hours:
|
||||||
cutoff: datetime = datetime.utcnow() - timedelta(hours=args.most_recent_hours)
|
cutoff: datetime = mango.utc_now() - timedelta(hours=args.most_recent_hours)
|
||||||
cutoff = cutoff.replace(tzinfo=timezone(offset=timedelta()))
|
cutoff = cutoff.replace(tzinfo=timezone(offset=timedelta()))
|
||||||
history.download_latest(context, account, cutoff)
|
history.download_latest(context, account, cutoff)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -90,7 +90,7 @@ parser.add_argument(
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--existing-order-time-in-force-tolerance",
|
"--existing-order-time-in-force-tolerance",
|
||||||
type=Decimal,
|
type=Decimal,
|
||||||
default=Decimal("0.001"),
|
default=Decimal(0),
|
||||||
help="tolerance in time-in-force when matching existing orders or cancelling/replacing",
|
help="tolerance in time-in-force when matching existing orders or cancelling/replacing",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -187,7 +187,7 @@ def cleanup(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
cancels: mango.CombinableInstructions = mango.CombinableInstructions.empty()
|
cancels: mango.CombinableInstructions = mango.CombinableInstructions.empty()
|
||||||
orders = market_operations.load_my_orders()
|
orders = market_operations.load_my_orders(include_expired=True)
|
||||||
for order in orders:
|
for order in orders:
|
||||||
cancels += market_instruction_builder.build_cancel_order_instructions(
|
cancels += market_instruction_builder.build_cancel_order_instructions(
|
||||||
order, ok_if_missing=True
|
order, ok_if_missing=True
|
||||||
|
|
|
@ -29,6 +29,12 @@ parser.add_argument(
|
||||||
default=False,
|
default=False,
|
||||||
help="runs as read-only and does not perform any transactions",
|
help="runs as read-only and does not perform any transactions",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--include-expired",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="show all owned transactions, even those that have expired",
|
||||||
|
)
|
||||||
args: argparse.Namespace = mango.parse_args(parser)
|
args: argparse.Namespace = mango.parse_args(parser)
|
||||||
|
|
||||||
context = mango.ContextBuilder.from_command_line_parameters(args)
|
context = mango.ContextBuilder.from_command_line_parameters(args)
|
||||||
|
@ -45,7 +51,7 @@ if market is None:
|
||||||
market_operations = mango.create_market_operations(
|
market_operations = mango.create_market_operations(
|
||||||
context, wallet, account, market, args.dry_run
|
context, wallet, account, market, args.dry_run
|
||||||
)
|
)
|
||||||
orders = market_operations.load_my_orders()
|
orders = market_operations.load_my_orders(include_expired=args.include_expired)
|
||||||
mango.output(f"{len(orders)} order(s) to show.")
|
mango.output(f"{len(orders)} order(s) to show.")
|
||||||
for order in orders:
|
for order in orders:
|
||||||
mango.output(order)
|
mango.output(order)
|
||||||
|
|
|
@ -67,7 +67,7 @@ try:
|
||||||
bid, ask = self.calculate_order_prices(price)
|
bid, ask = self.calculate_order_prices(price)
|
||||||
buy_quantity, sell_quantity = self.calculate_order_quantities(price, inventory)
|
buy_quantity, sell_quantity = self.calculate_order_quantities(price, inventory)
|
||||||
|
|
||||||
current_orders = self.market_operations.load_my_orders()
|
current_orders = self.market_operations.load_my_orders(include_expired=True)
|
||||||
buy_orders = [order for order in current_orders if order.side == mango.Side.BUY]
|
buy_orders = [order for order in current_orders if order.side == mango.Side.BUY]
|
||||||
if self.orders_require_action(buy_orders, bid, buy_quantity):
|
if self.orders_require_action(buy_orders, bid, buy_quantity):
|
||||||
self._logger.info("Cancelling BUY orders.")
|
self._logger.info("Cancelling BUY orders.")
|
||||||
|
|
|
@ -64,6 +64,8 @@ from .createmarketoperations import (
|
||||||
create_market_instruction_builder as create_market_instruction_builder,
|
create_market_instruction_builder as create_market_instruction_builder,
|
||||||
)
|
)
|
||||||
from .createmarketoperations import create_market_operations as create_market_operations
|
from .createmarketoperations import create_market_operations as create_market_operations
|
||||||
|
from .datetimes import local_now as local_now
|
||||||
|
from .datetimes import utc_now as utc_now
|
||||||
from .encoding import decode_binary as decode_binary
|
from .encoding import decode_binary as decode_binary
|
||||||
from .encoding import encode_binary as encode_binary
|
from .encoding import encode_binary as encode_binary
|
||||||
from .encoding import encode_key as encode_key
|
from .encoding import encode_key as encode_key
|
||||||
|
|
|
@ -45,6 +45,7 @@ from solana.rpc.types import (
|
||||||
from solana.transaction import Transaction
|
from solana.transaction import Transaction
|
||||||
|
|
||||||
from .constants import SOL_DECIMAL_DIVISOR
|
from .constants import SOL_DECIMAL_DIVISOR
|
||||||
|
from .datetimes import local_now
|
||||||
from .instructionreporter import InstructionReporter
|
from .instructionreporter import InstructionReporter
|
||||||
from .logmessages import expand_log_messages
|
from .logmessages import expand_log_messages
|
||||||
from .text import indent_collection_as_str
|
from .text import indent_collection_as_str
|
||||||
|
@ -394,7 +395,7 @@ class TransactionWatcher:
|
||||||
self.collector = collector
|
self.collector = collector
|
||||||
|
|
||||||
def report_on_transaction(self) -> None:
|
def report_on_transaction(self) -> None:
|
||||||
started_at: datetime = datetime.now()
|
started_at: datetime = local_now()
|
||||||
for pause in [
|
for pause in [
|
||||||
0.1,
|
0.1,
|
||||||
0.2,
|
0.2,
|
||||||
|
@ -438,7 +439,7 @@ class TransactionWatcher:
|
||||||
):
|
):
|
||||||
[status] = transaction_response["result"]["value"]
|
[status] = transaction_response["result"]["value"]
|
||||||
if status is not None:
|
if status is not None:
|
||||||
delta: timedelta = datetime.now() - started_at
|
delta: timedelta = local_now() - started_at
|
||||||
time_taken: float = delta.seconds + delta.microseconds / 1000000
|
time_taken: float = delta.seconds + delta.microseconds / 1000000
|
||||||
|
|
||||||
# value should be a dict that looks like:
|
# value should be a dict that looks like:
|
||||||
|
@ -493,7 +494,7 @@ class TransactionWatcher:
|
||||||
return
|
return
|
||||||
time.sleep(pause)
|
time.sleep(pause)
|
||||||
|
|
||||||
delta = datetime.now() - started_at
|
delta = local_now() - started_at
|
||||||
time_wasted_looking: float = delta.seconds + delta.microseconds / 1000000
|
time_wasted_looking: float = delta.seconds + delta.microseconds / 1000000
|
||||||
self.collector.add_transaction(
|
self.collector.add_transaction(
|
||||||
TransactionStatus(
|
TransactionStatus(
|
||||||
|
@ -1163,15 +1164,15 @@ class BetterClient:
|
||||||
f"Waiting up to {max_wait_in_seconds} seconds for {transaction_ids}."
|
f"Waiting up to {max_wait_in_seconds} seconds for {transaction_ids}."
|
||||||
)
|
)
|
||||||
all_confirmed: typing.List[str] = []
|
all_confirmed: typing.List[str] = []
|
||||||
start_time: datetime = datetime.now()
|
start_time: datetime = local_now()
|
||||||
cutoff: datetime = start_time + timedelta(seconds=max_wait_in_seconds)
|
cutoff: datetime = start_time + timedelta(seconds=max_wait_in_seconds)
|
||||||
for transaction_id in transaction_ids:
|
for transaction_id in transaction_ids:
|
||||||
while datetime.now() < cutoff:
|
while local_now() < cutoff:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
confirmed = self.get_confirmed_transaction(transaction_id)
|
confirmed = self.get_confirmed_transaction(transaction_id)
|
||||||
if confirmed is not None:
|
if confirmed is not None:
|
||||||
self._logger.info(
|
self._logger.info(
|
||||||
f"Confirmed {transaction_id} after {datetime.now() - start_time} seconds."
|
f"Confirmed {transaction_id} after {local_now() - start_time} seconds."
|
||||||
)
|
)
|
||||||
all_confirmed += [transaction_id]
|
all_confirmed += [transaction_id]
|
||||||
break
|
break
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# # ⚠ 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 datetime import datetime, timezone
|
||||||
|
|
||||||
|
# # 🥭 Datetimes
|
||||||
|
#
|
||||||
|
# This file contains a few useful datetime functions.
|
||||||
|
#
|
||||||
|
# They exist solely to make clear what is being used when called. There are 2 general cases:
|
||||||
|
# 1. Logging/time tracking/user output - local time is what should be used.
|
||||||
|
# 2. Comparison with dates stored on-chain - UTC should be used.
|
||||||
|
#
|
||||||
|
# Getting a UTC time that is comparable with on-chain datetimes isn't intuitive, so putting
|
||||||
|
# it in one place and using that consistently should make it clearer in the calling code
|
||||||
|
# what is going on, without the unnecessary complications.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
def local_now() -> datetime:
|
||||||
|
return datetime.now()
|
||||||
|
|
||||||
|
|
||||||
|
def utc_now() -> datetime:
|
||||||
|
return datetime.utcnow().astimezone(timezone.utc)
|
|
@ -18,7 +18,6 @@ import mango
|
||||||
import traceback
|
import traceback
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from .hedger import Hedger
|
from .hedger import Hedger
|
||||||
|
@ -163,7 +162,7 @@ class PerpToSpotHedger(Hedger):
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
self.pulse_complete.on_next(datetime.now())
|
self.pulse_complete.on_next(mango.local_now())
|
||||||
except (
|
except (
|
||||||
mango.RateLimitException,
|
mango.RateLimitException,
|
||||||
mango.NodeIsBehindException,
|
mango.NodeIsBehindException,
|
||||||
|
|
|
@ -24,6 +24,7 @@ from decimal import Decimal
|
||||||
from .account import Account
|
from .account import Account
|
||||||
from .accountliquidator import AccountLiquidator
|
from .accountliquidator import AccountLiquidator
|
||||||
from .context import Context
|
from .context import Context
|
||||||
|
from .datetimes import local_now
|
||||||
from .group import Group
|
from .group import Group
|
||||||
from .instrumentvalue import InstrumentValue
|
from .instrumentvalue import InstrumentValue
|
||||||
from .liquidatablereport import LiquidatableReport, LiquidatableState
|
from .liquidatablereport import LiquidatableReport, LiquidatableState
|
||||||
|
@ -79,8 +80,8 @@ class LiquidationProcessor:
|
||||||
LiquidationEvent
|
LiquidationEvent
|
||||||
]()
|
]()
|
||||||
self.ripe_accounts: typing.Optional[typing.Sequence[Account]] = None
|
self.ripe_accounts: typing.Optional[typing.Sequence[Account]] = None
|
||||||
self.ripe_accounts_updated_at: datetime = datetime.now()
|
self.ripe_accounts_updated_at: datetime = local_now()
|
||||||
self.prices_updated_at: datetime = datetime.now()
|
self.prices_updated_at: datetime = local_now()
|
||||||
self.state: LiquidationProcessorState = LiquidationProcessorState.STARTING
|
self.state: LiquidationProcessorState = LiquidationProcessorState.STARTING
|
||||||
self.state_change: EventSource[LiquidationProcessor] = EventSource[
|
self.state_change: EventSource[LiquidationProcessor] = EventSource[
|
||||||
LiquidationProcessor
|
LiquidationProcessor
|
||||||
|
@ -92,7 +93,7 @@ class LiquidationProcessor:
|
||||||
)
|
)
|
||||||
self._check_update_recency("prices", self.prices_updated_at)
|
self._check_update_recency("prices", self.prices_updated_at)
|
||||||
self.ripe_accounts = ripe_accounts
|
self.ripe_accounts = ripe_accounts
|
||||||
self.ripe_accounts_updated_at = datetime.now()
|
self.ripe_accounts_updated_at = local_now()
|
||||||
# If this is the first time through, mark ourselves as Healthy.
|
# If this is the first time through, mark ourselves as Healthy.
|
||||||
if self.state == LiquidationProcessorState.STARTING:
|
if self.state == LiquidationProcessorState.STARTING:
|
||||||
self.state = LiquidationProcessorState.HEALTHY
|
self.state = LiquidationProcessorState.HEALTHY
|
||||||
|
@ -160,7 +161,7 @@ class LiquidationProcessor:
|
||||||
|
|
||||||
self._liquidate_all(group, prices, worthwhile)
|
self._liquidate_all(group, prices, worthwhile)
|
||||||
|
|
||||||
self.prices_updated_at = datetime.now()
|
self.prices_updated_at = local_now()
|
||||||
time_taken = time.time() - started_at
|
time_taken = time.time() - started_at
|
||||||
self._logger.info(
|
self._logger.info(
|
||||||
f"Check of all ripe 🥭 accounts complete. Time taken: {time_taken:.2f} seconds."
|
f"Check of all ripe 🥭 accounts complete. Time taken: {time_taken:.2f} seconds."
|
||||||
|
@ -215,7 +216,7 @@ class LiquidationProcessor:
|
||||||
)
|
)
|
||||||
|
|
||||||
def _check_update_recency(self, name: str, last_updated_at: datetime) -> None:
|
def _check_update_recency(self, name: str, last_updated_at: datetime) -> None:
|
||||||
how_long_ago_was_last_update = datetime.now() - last_updated_at
|
how_long_ago_was_last_update = local_now() - last_updated_at
|
||||||
if how_long_ago_was_last_update > LiquidationProcessor._AGE_ERROR_THRESHOLD:
|
if how_long_ago_was_last_update > LiquidationProcessor._AGE_ERROR_THRESHOLD:
|
||||||
self.state = LiquidationProcessorState.UNHEALTHY
|
self.state = LiquidationProcessorState.UNHEALTHY
|
||||||
self.state_change.on_next(self)
|
self.state_change.on_next(self)
|
||||||
|
|
|
@ -155,7 +155,7 @@ Ignore:
|
||||||
payer + cancellations + place_orders + crank + settle + redeem
|
payer + cancellations + place_orders + crank + settle + redeem
|
||||||
).execute(context)
|
).execute(context)
|
||||||
|
|
||||||
self.pulse_complete.on_next(datetime.now())
|
self.pulse_complete.on_next(mango.local_now())
|
||||||
except (
|
except (
|
||||||
mango.RateLimitException,
|
mango.RateLimitException,
|
||||||
mango.NodeIsBehindException,
|
mango.NodeIsBehindException,
|
||||||
|
|
|
@ -139,7 +139,7 @@ class MarketOperations(metaclass=abc.ABCMeta):
|
||||||
)
|
)
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def load_my_orders(self) -> typing.Sequence[Order]:
|
def load_my_orders(self, include_expired: bool = False) -> typing.Sequence[Order]:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"MarketOperations.load_my_orders() is not implemented on the base type."
|
"MarketOperations.load_my_orders() is not implemented on the base type."
|
||||||
)
|
)
|
||||||
|
@ -228,7 +228,7 @@ class NullMarketOperations(MarketOperations):
|
||||||
def load_orderbook(self) -> OrderBook:
|
def load_orderbook(self) -> OrderBook:
|
||||||
return OrderBook(self.market_name, NullLotSizeConverter(), [], [])
|
return OrderBook(self.market_name, NullLotSizeConverter(), [], [])
|
||||||
|
|
||||||
def load_my_orders(self) -> typing.Sequence[Order]:
|
def load_my_orders(self, include_expired: bool = False) -> typing.Sequence[Order]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def settle(self) -> typing.Sequence[str]:
|
def settle(self) -> typing.Sequence[str]:
|
||||||
|
|
|
@ -135,9 +135,9 @@ class ModelState:
|
||||||
return self.event_queue_watcher.latest.accounts_to_crank
|
return self.event_queue_watcher.latest.accounts_to_crank
|
||||||
|
|
||||||
def current_orders(self) -> typing.Sequence[Order]:
|
def current_orders(self) -> typing.Sequence[Order]:
|
||||||
self.orderbook
|
return self.orderbook.all_orders_for_owner(
|
||||||
all_orders = [*self.bids, *self.asks]
|
self.order_owner, include_expired=True
|
||||||
return list([o for o in all_orders if o.owner == self.order_owner])
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"""« ModelState for market '{self.market.symbol}'
|
return f"""« ModelState for market '{self.market.symbol}'
|
||||||
|
|
|
@ -23,6 +23,7 @@ from datetime import datetime
|
||||||
from rx.core.typing import Disposable
|
from rx.core.typing import Disposable
|
||||||
from rxpy_backpressure import BackPressure
|
from rxpy_backpressure import BackPressure
|
||||||
|
|
||||||
|
from .datetimes import local_now
|
||||||
from .output import output
|
from .output import output
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ class TimestampedPrintingObserverSubscriber(PrintingObserverSubscriber):
|
||||||
super().__init__(report_no_output)
|
super().__init__(report_no_output)
|
||||||
|
|
||||||
def on_next(self, item: typing.Any) -> None:
|
def on_next(self, item: typing.Any) -> None:
|
||||||
super().on_next(f"{datetime.now()}: {item}")
|
super().on_next(f"{local_now()}: {item}")
|
||||||
|
|
||||||
|
|
||||||
# # 🥭 CollectingObserverSubscriber class
|
# # 🥭 CollectingObserverSubscriber class
|
||||||
|
@ -140,11 +141,11 @@ class LatestItemObserverSubscriber(
|
||||||
def __init__(self, initial: TItem) -> None:
|
def __init__(self, initial: TItem) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.latest: TItem = initial
|
self.latest: TItem = initial
|
||||||
self.update_timestamp: datetime = datetime.now()
|
self.update_timestamp: datetime = local_now()
|
||||||
|
|
||||||
def on_next(self, item: TItem) -> None:
|
def on_next(self, item: TItem) -> None:
|
||||||
self.latest = item
|
self.latest = item
|
||||||
self.update_timestamp = datetime.now()
|
self.update_timestamp = local_now()
|
||||||
|
|
||||||
def on_error(self, ex: Exception) -> None:
|
def on_error(self, ex: Exception) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -25,6 +25,7 @@ from decimal import Decimal
|
||||||
from rx.subject.subject import Subject
|
from rx.subject.subject import Subject
|
||||||
|
|
||||||
from ...context import Context
|
from ...context import Context
|
||||||
|
from ...datetimes import utc_now
|
||||||
from ...market import Market
|
from ...market import Market
|
||||||
from ...observables import DisposePropagator, DisposeWrapper
|
from ...observables import DisposePropagator, DisposeWrapper
|
||||||
from ...oracle import (
|
from ...oracle import (
|
||||||
|
@ -79,7 +80,7 @@ class FtxOracle(Oracle):
|
||||||
|
|
||||||
return Price(
|
return Price(
|
||||||
self.source,
|
self.source,
|
||||||
datetime.now(),
|
utc_now(),
|
||||||
self.market,
|
self.market,
|
||||||
bid,
|
bid,
|
||||||
price,
|
price,
|
||||||
|
|
|
@ -18,10 +18,10 @@ import rx
|
||||||
import rx.operators as ops
|
import rx.operators as ops
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from ...context import Context
|
from ...context import Context
|
||||||
|
from ...datetimes import utc_now
|
||||||
from ...ensuremarketloaded import ensure_market_loaded
|
from ...ensuremarketloaded import ensure_market_loaded
|
||||||
from ...loadedmarket import LoadedMarket
|
from ...loadedmarket import LoadedMarket
|
||||||
from ...market import Market
|
from ...market import Market
|
||||||
|
@ -86,7 +86,7 @@ class MarketOracle(Oracle):
|
||||||
|
|
||||||
return Price(
|
return Price(
|
||||||
self.source,
|
self.source,
|
||||||
datetime.now(),
|
utc_now(),
|
||||||
self.market,
|
self.market,
|
||||||
top_bid,
|
top_bid,
|
||||||
mid_price,
|
mid_price,
|
||||||
|
|
|
@ -19,12 +19,12 @@ import rx
|
||||||
import rx.operators as ops
|
import rx.operators as ops
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
|
|
||||||
from ...accountinfo import AccountInfo
|
from ...accountinfo import AccountInfo
|
||||||
from ...context import Context
|
from ...context import Context
|
||||||
|
from ...datetimes import utc_now
|
||||||
from ...market import Market
|
from ...market import Market
|
||||||
from ...observables import observable_pipeline_error_reporter
|
from ...observables import observable_pipeline_error_reporter
|
||||||
from ...oracle import (
|
from ...oracle import (
|
||||||
|
@ -110,7 +110,13 @@ class PythOracle(Oracle):
|
||||||
|
|
||||||
# Pyth has no notion of bids, asks, or spreads so just provide the single price.
|
# Pyth has no notion of bids, asks, or spreads so just provide the single price.
|
||||||
return Price(
|
return Price(
|
||||||
self.source, datetime.now(), self.market, price, price, price, confidence
|
self.source,
|
||||||
|
utc_now(),
|
||||||
|
self.market,
|
||||||
|
price,
|
||||||
|
price,
|
||||||
|
price,
|
||||||
|
confidence,
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_streaming_observable(
|
def to_streaming_observable(
|
||||||
|
|
|
@ -18,12 +18,12 @@ import rx
|
||||||
import rx.operators as ops
|
import rx.operators as ops
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
|
|
||||||
from ...cache import Cache
|
from ...cache import Cache
|
||||||
from ...context import Context
|
from ...context import Context
|
||||||
|
from ...datetimes import utc_now
|
||||||
from ...ensuremarketloaded import ensure_market_loaded
|
from ...ensuremarketloaded import ensure_market_loaded
|
||||||
from ...market import Market
|
from ...market import Market
|
||||||
from ...observables import observable_pipeline_error_reporter
|
from ...observables import observable_pipeline_error_reporter
|
||||||
|
@ -83,7 +83,7 @@ class StubOracle(Oracle):
|
||||||
# will give you the consistent results, but you'll need to adjust your code"
|
# will give you the consistent results, but you'll need to adjust your code"
|
||||||
return Price(
|
return Price(
|
||||||
self.source,
|
self.source,
|
||||||
datetime.now(),
|
utc_now(),
|
||||||
self.market,
|
self.market,
|
||||||
raw_price.price,
|
raw_price.price,
|
||||||
raw_price.price,
|
raw_price.price,
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import enum
|
import enum
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import timedelta, timezone
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
|
|
||||||
|
@ -141,51 +141,47 @@ class PerpOrderBookSide(AddressableAccount):
|
||||||
|
|
||||||
stack = [self.root_node]
|
stack = [self.root_node]
|
||||||
orders: typing.List[Order] = []
|
orders: typing.List[Order] = []
|
||||||
now: datetime = datetime.utcnow().astimezone(timezone.utc)
|
|
||||||
while len(stack) > 0:
|
while len(stack) > 0:
|
||||||
index = int(stack.pop())
|
index = int(stack.pop())
|
||||||
node = self.nodes[index]
|
node = self.nodes[index]
|
||||||
if node.type_name == "leaf":
|
if node.type_name == "leaf":
|
||||||
expiration = (
|
expiration = Order.NoExpiration
|
||||||
node.timestamp.astimezone(timezone.utc)
|
if node.time_in_force != 0:
|
||||||
+ timedelta(seconds=float(node.time_in_force))
|
expiration = node.timestamp.astimezone(timezone.utc) + timedelta(
|
||||||
if node.time_in_force == 0
|
seconds=float(node.time_in_force)
|
||||||
else Order.NoExpiration
|
)
|
||||||
|
|
||||||
|
price = node.key["price"]
|
||||||
|
quantity = node.quantity
|
||||||
|
|
||||||
|
decimals_differential = (
|
||||||
|
self.perp_market_details.base_instrument.decimals
|
||||||
|
- self.perp_market_details.quote_token.token.decimals
|
||||||
)
|
)
|
||||||
if (node.time_in_force == 0) or (expiration > now):
|
native_to_ui = Decimal(10) ** decimals_differential
|
||||||
price = node.key["price"]
|
quote_lot_size = self.perp_market_details.quote_lot_size
|
||||||
quantity = node.quantity
|
base_lot_size = self.perp_market_details.base_lot_size
|
||||||
|
actual_price = price * (quote_lot_size / base_lot_size) * native_to_ui
|
||||||
|
|
||||||
decimals_differential = (
|
base_factor = (
|
||||||
self.perp_market_details.base_instrument.decimals
|
Decimal(10) ** self.perp_market_details.base_instrument.decimals
|
||||||
- self.perp_market_details.quote_token.token.decimals
|
)
|
||||||
)
|
actual_quantity = (
|
||||||
native_to_ui = Decimal(10) ** decimals_differential
|
quantity * self.perp_market_details.base_lot_size
|
||||||
quote_lot_size = self.perp_market_details.quote_lot_size
|
) / base_factor
|
||||||
base_lot_size = self.perp_market_details.base_lot_size
|
|
||||||
actual_price = (
|
|
||||||
price * (quote_lot_size / base_lot_size) * native_to_ui
|
|
||||||
)
|
|
||||||
|
|
||||||
base_factor = (
|
orders += [
|
||||||
Decimal(10) ** self.perp_market_details.base_instrument.decimals
|
Order(
|
||||||
|
int(node.key["order_id"]),
|
||||||
|
node.client_order_id,
|
||||||
|
node.owner,
|
||||||
|
order_side,
|
||||||
|
actual_price,
|
||||||
|
actual_quantity,
|
||||||
|
OrderType.UNKNOWN,
|
||||||
|
expiration=expiration,
|
||||||
)
|
)
|
||||||
actual_quantity = (
|
]
|
||||||
quantity * self.perp_market_details.base_lot_size
|
|
||||||
) / base_factor
|
|
||||||
|
|
||||||
orders += [
|
|
||||||
Order(
|
|
||||||
int(node.key["order_id"]),
|
|
||||||
node.client_order_id,
|
|
||||||
node.owner,
|
|
||||||
order_side,
|
|
||||||
actual_price,
|
|
||||||
actual_quantity,
|
|
||||||
OrderType.UNKNOWN,
|
|
||||||
expiration=expiration,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
elif node.type_name == "inner":
|
elif node.type_name == "inner":
|
||||||
if order_side == Side.BUY:
|
if order_side == Side.BUY:
|
||||||
stack = [*stack, node.children[0], node.children[1]]
|
stack = [*stack, node.children[0], node.children[1]]
|
||||||
|
|
|
@ -26,6 +26,7 @@ from pyserum.market.types import Order as PySerumOrder
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
|
|
||||||
from .constants import SYSTEM_PROGRAM_ADDRESS
|
from .constants import SYSTEM_PROGRAM_ADDRESS
|
||||||
|
from .datetimes import utc_now
|
||||||
from .lotsizeconverter import LotSizeConverter
|
from .lotsizeconverter import LotSizeConverter
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,6 +171,12 @@ class Order:
|
||||||
high_order = id_bytes[8:]
|
high_order = id_bytes[8:]
|
||||||
return Decimal(int.from_bytes(high_order, "little", signed=False))
|
return Decimal(int.from_bytes(high_order, "little", signed=False))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expired(self) -> bool:
|
||||||
|
if (self.expiration == Order.NoExpiration) or (self.expiration > utc_now()):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
# Returns an identical order with the ID changed.
|
# Returns an identical order with the ID changed.
|
||||||
def with_id(self, id: int) -> "Order":
|
def with_id(self, id: int) -> "Order":
|
||||||
return Order(
|
return Order(
|
||||||
|
@ -388,7 +395,7 @@ class Order:
|
||||||
if expire_seconds is None or expire_seconds <= Decimal(0):
|
if expire_seconds is None or expire_seconds <= Decimal(0):
|
||||||
return Order.NoExpiration
|
return Order.NoExpiration
|
||||||
|
|
||||||
return datetime.now() + timedelta(seconds=float(expire_seconds))
|
return utc_now() + timedelta(seconds=float(expire_seconds))
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
owner: str = ""
|
owner: str = ""
|
||||||
|
@ -397,7 +404,7 @@ class Order:
|
||||||
order_type: str = ""
|
order_type: str = ""
|
||||||
if self.order_type != OrderType.UNKNOWN:
|
if self.order_type != OrderType.UNKNOWN:
|
||||||
order_type = f" {self.order_type}"
|
order_type = f" {self.order_type}"
|
||||||
return f"« Order {owner}{self.side} for {self.quantity:,.8f} at {self.price:.8f} [ID: {self.id} / {self.client_id}]{order_type}{' reduceOnly' if self.reduce_only else ''}»"
|
return f"« Order {owner}{self.side} for {self.quantity:,.8f} at {self.price:.8f} [ID: {self.id} / {self.client_id}]{order_type}{' reduceOnly' if self.reduce_only else ''}{' EXPIRED' if self.expired else ''} »"
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"{self}"
|
return f"{self}"
|
||||||
|
@ -420,7 +427,7 @@ class OrderBook:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bids(self) -> typing.Sequence[Order]:
|
def bids(self) -> typing.Sequence[Order]:
|
||||||
return self.__bids
|
return list([o for o in self.__bids if not o.expired])
|
||||||
|
|
||||||
@bids.setter
|
@bids.setter
|
||||||
def bids(self, bids: typing.Sequence[Order]) -> None:
|
def bids(self, bids: typing.Sequence[Order]) -> None:
|
||||||
|
@ -431,7 +438,7 @@ class OrderBook:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def asks(self) -> typing.Sequence[Order]:
|
def asks(self) -> typing.Sequence[Order]:
|
||||||
return self.__asks
|
return list([o for o in self.__asks if not o.expired])
|
||||||
|
|
||||||
@asks.setter
|
@asks.setter
|
||||||
def asks(self, asks: typing.Sequence[Order]) -> None:
|
def asks(self, asks: typing.Sequence[Order]) -> None:
|
||||||
|
@ -443,28 +450,32 @@ class OrderBook:
|
||||||
# The top bid is the highest price someone is willing to pay to BUY
|
# The top bid is the highest price someone is willing to pay to BUY
|
||||||
@property
|
@property
|
||||||
def top_bid(self) -> typing.Optional[Order]:
|
def top_bid(self) -> typing.Optional[Order]:
|
||||||
if self.bids and len(self.bids) > 0:
|
bids = self.bids
|
||||||
|
if bids and len(bids) > 0:
|
||||||
# Top-of-book is always at index 0 for us.
|
# Top-of-book is always at index 0 for us.
|
||||||
return self.bids[0]
|
return bids[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# The top ask is the lowest price someone is willing to pay to SELL
|
# The top ask is the lowest price someone is willing to pay to SELL
|
||||||
@property
|
@property
|
||||||
def top_ask(self) -> typing.Optional[Order]:
|
def top_ask(self) -> typing.Optional[Order]:
|
||||||
if self.asks and len(self.asks) > 0:
|
asks = self.asks
|
||||||
|
if asks and len(asks) > 0:
|
||||||
# Top-of-book is always at index 0 for us.
|
# Top-of-book is always at index 0 for us.
|
||||||
return self.asks[0]
|
return asks[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# The mid price is halfway between the best bid and best ask.
|
# The mid price is halfway between the best bid and best ask.
|
||||||
@property
|
@property
|
||||||
def mid_price(self) -> typing.Optional[Decimal]:
|
def mid_price(self) -> typing.Optional[Decimal]:
|
||||||
if self.top_bid is not None and self.top_ask is not None:
|
top_bid = self.top_bid
|
||||||
return (self.top_bid.price + self.top_ask.price) / 2
|
top_ask = self.top_ask
|
||||||
elif self.top_bid is not None:
|
if top_bid is not None and top_ask is not None:
|
||||||
return self.top_bid.price
|
return (top_bid.price + top_ask.price) / 2
|
||||||
elif self.top_ask is not None:
|
elif top_bid is not None:
|
||||||
return self.top_ask.price
|
return top_bid.price
|
||||||
|
elif top_ask is not None:
|
||||||
|
return top_ask.price
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -476,8 +487,17 @@ class OrderBook:
|
||||||
else:
|
else:
|
||||||
return top_ask.price - top_bid.price
|
return top_ask.price - top_bid.price
|
||||||
|
|
||||||
def all_orders_for_owner(self, owner_address: PublicKey) -> typing.Sequence[Order]:
|
def all_orders(self, include_expired: bool = False) -> typing.Sequence[Order]:
|
||||||
return list([o for o in [*self.bids, *self.asks] if o.owner == owner_address])
|
if include_expired:
|
||||||
|
return [*self.__bids, *self.__asks]
|
||||||
|
return [*self.bids, *self.asks]
|
||||||
|
|
||||||
|
def all_orders_for_owner(
|
||||||
|
self, owner_address: PublicKey, include_expired: bool = False
|
||||||
|
) -> typing.Sequence[Order]:
|
||||||
|
return list(
|
||||||
|
[o for o in self.all_orders(include_expired) if o.owner == owner_address]
|
||||||
|
)
|
||||||
|
|
||||||
def to_dataframe(self) -> pandas.DataFrame:
|
def to_dataframe(self) -> pandas.DataFrame:
|
||||||
column_mapper = {
|
column_mapper = {
|
||||||
|
@ -487,6 +507,7 @@ class OrderBook:
|
||||||
"side": "Side",
|
"side": "Side",
|
||||||
"price": "Price",
|
"price": "Price",
|
||||||
"quantity": "Quantity",
|
"quantity": "Quantity",
|
||||||
|
"expiration": "Expiration",
|
||||||
}
|
}
|
||||||
|
|
||||||
frame: pandas.DataFrame = pandas.DataFrame([*reversed(self.bids), *self.asks])
|
frame: pandas.DataFrame = pandas.DataFrame([*reversed(self.bids), *self.asks])
|
||||||
|
|
|
@ -15,13 +15,14 @@
|
||||||
|
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
|
|
||||||
from .accountinfo import AccountInfo
|
from .accountinfo import AccountInfo
|
||||||
from .addressableaccount import AddressableAccount
|
from .addressableaccount import AddressableAccount
|
||||||
from .context import Context
|
from .context import Context
|
||||||
|
from .datetimes import utc_now
|
||||||
from .group import GroupSlot, Group
|
from .group import GroupSlot, Group
|
||||||
from .instrumentvalue import InstrumentValue
|
from .instrumentvalue import InstrumentValue
|
||||||
from .layouts import layouts
|
from .layouts import layouts
|
||||||
|
@ -87,7 +88,7 @@ class LiquidityMiningInfo:
|
||||||
# portion_given = 1 - mngoLeft / mngoPerPeriod
|
# portion_given = 1 - mngoLeft / mngoPerPeriod
|
||||||
# elapsed = (<current_time> - periodStart) / targetPeriodLength
|
# elapsed = (<current_time> - periodStart) / targetPeriodLength
|
||||||
# est_next = elapsed / portion_given - elapsed
|
# est_next = elapsed / portion_given - elapsed
|
||||||
now: datetime = datetime.now().replace(microsecond=0).astimezone(timezone.utc)
|
now: datetime = utc_now().replace(microsecond=0)
|
||||||
mngo_distributed: InstrumentValue = self.mngo_per_period - self.mngo_left
|
mngo_distributed: InstrumentValue = self.mngo_per_period - self.mngo_left
|
||||||
proportion_distributed: Decimal = Decimal(0)
|
proportion_distributed: Decimal = Decimal(0)
|
||||||
elapsed: timedelta = now - self.period_start
|
elapsed: timedelta = now - self.period_start
|
||||||
|
|
|
@ -264,9 +264,11 @@ class PerpMarketOperations(MarketOperations):
|
||||||
def load_orderbook(self) -> OrderBook:
|
def load_orderbook(self) -> OrderBook:
|
||||||
return self.perp_market.fetch_orderbook(self.context)
|
return self.perp_market.fetch_orderbook(self.context)
|
||||||
|
|
||||||
def load_my_orders(self) -> typing.Sequence[Order]:
|
def load_my_orders(self, include_expired: bool = False) -> typing.Sequence[Order]:
|
||||||
orderbook: OrderBook = self.load_orderbook()
|
orderbook: OrderBook = self.load_orderbook()
|
||||||
return orderbook.all_orders_for_owner(self.account.address)
|
return orderbook.all_orders_for_owner(
|
||||||
|
self.account.address, include_expired=include_expired
|
||||||
|
)
|
||||||
|
|
||||||
def _build_crank(
|
def _build_crank(
|
||||||
self, limit: Decimal = Decimal(32), add_self: bool = False
|
self, limit: Decimal = Decimal(32), add_self: bool = False
|
||||||
|
|
|
@ -21,9 +21,10 @@ import rx.subject
|
||||||
import typing
|
import typing
|
||||||
import websocket
|
import websocket
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
from .datetimes import local_now
|
||||||
|
|
||||||
|
|
||||||
# # 🥭 ReconnectingWebsocket class
|
# # 🥭 ReconnectingWebsocket class
|
||||||
#
|
#
|
||||||
|
@ -41,16 +42,16 @@ class ReconnectingWebsocket:
|
||||||
self.reconnect_required: bool = True
|
self.reconnect_required: bool = True
|
||||||
self.ping_interval: int = 0
|
self.ping_interval: int = 0
|
||||||
self.connecting: rx.subject.behaviorsubject.BehaviorSubject = (
|
self.connecting: rx.subject.behaviorsubject.BehaviorSubject = (
|
||||||
rx.subject.behaviorsubject.BehaviorSubject(datetime.now())
|
rx.subject.behaviorsubject.BehaviorSubject(local_now())
|
||||||
)
|
)
|
||||||
self.disconnected: rx.subject.behaviorsubject.BehaviorSubject = (
|
self.disconnected: rx.subject.behaviorsubject.BehaviorSubject = (
|
||||||
rx.subject.behaviorsubject.BehaviorSubject(datetime.now())
|
rx.subject.behaviorsubject.BehaviorSubject(local_now())
|
||||||
)
|
)
|
||||||
self.ping: rx.subject.behaviorsubject.BehaviorSubject = (
|
self.ping: rx.subject.behaviorsubject.BehaviorSubject = (
|
||||||
rx.subject.behaviorsubject.BehaviorSubject(datetime.now())
|
rx.subject.behaviorsubject.BehaviorSubject(local_now())
|
||||||
)
|
)
|
||||||
self.pong: rx.subject.behaviorsubject.BehaviorSubject = (
|
self.pong: rx.subject.behaviorsubject.BehaviorSubject = (
|
||||||
rx.subject.behaviorsubject.BehaviorSubject(datetime.now())
|
rx.subject.behaviorsubject.BehaviorSubject(local_now())
|
||||||
)
|
)
|
||||||
self.item: rx.subject.subject.Subject = rx.subject.subject.Subject()
|
self.item: rx.subject.subject.Subject = rx.subject.subject.Subject()
|
||||||
|
|
||||||
|
@ -76,10 +77,10 @@ class ReconnectingWebsocket:
|
||||||
self._logger.warning(f"WebSocket for {self.url} has error {args}")
|
self._logger.warning(f"WebSocket for {self.url} has error {args}")
|
||||||
|
|
||||||
def _on_ping(self, *_: typing.Any) -> None:
|
def _on_ping(self, *_: typing.Any) -> None:
|
||||||
self.ping.on_next(datetime.now())
|
self.ping.on_next(local_now())
|
||||||
|
|
||||||
def _on_pong(self, *_: typing.Any) -> None:
|
def _on_pong(self, *_: typing.Any) -> None:
|
||||||
self.pong.on_next(datetime.now())
|
self.pong.on_next(local_now())
|
||||||
|
|
||||||
def open(self) -> None:
|
def open(self) -> None:
|
||||||
thread = Thread(target=self._run)
|
thread = Thread(target=self._run)
|
||||||
|
@ -91,7 +92,7 @@ class ReconnectingWebsocket:
|
||||||
def _run(self) -> None:
|
def _run(self) -> None:
|
||||||
while self.reconnect_required:
|
while self.reconnect_required:
|
||||||
self._logger.info(f"WebSocket connecting to: {self.url}")
|
self._logger.info(f"WebSocket connecting to: {self.url}")
|
||||||
self.connecting.on_next(datetime.now())
|
self.connecting.on_next(local_now())
|
||||||
self._ws = websocket.WebSocketApp(
|
self._ws = websocket.WebSocketApp(
|
||||||
self.url,
|
self.url,
|
||||||
on_open=self._on_open,
|
on_open=self._on_open,
|
||||||
|
@ -101,4 +102,4 @@ class ReconnectingWebsocket:
|
||||||
on_pong=self._on_pong,
|
on_pong=self._on_pong,
|
||||||
)
|
)
|
||||||
self._ws.run_forever(ping_interval=self.ping_interval)
|
self._ws.run_forever(ping_interval=self.ping_interval)
|
||||||
self.disconnected.on_next(datetime.now())
|
self.disconnected.on_next(local_now())
|
||||||
|
|
|
@ -357,13 +357,15 @@ class SerumMarketOperations(MarketOperations):
|
||||||
def load_orderbook(self) -> OrderBook:
|
def load_orderbook(self) -> OrderBook:
|
||||||
return self.serum_market.fetch_orderbook(self.context)
|
return self.serum_market.fetch_orderbook(self.context)
|
||||||
|
|
||||||
def load_my_orders(self) -> typing.Sequence[Order]:
|
def load_my_orders(self, include_expired: bool = False) -> typing.Sequence[Order]:
|
||||||
open_orders_address = self.market_instruction_builder.open_orders_address
|
open_orders_address = self.market_instruction_builder.open_orders_address
|
||||||
if not open_orders_address:
|
if not open_orders_address:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
orderbook: OrderBook = self.load_orderbook()
|
orderbook: OrderBook = self.load_orderbook()
|
||||||
return orderbook.all_orders_for_owner(open_orders_address)
|
return orderbook.all_orders_for_owner(
|
||||||
|
open_orders_address, include_expired=include_expired
|
||||||
|
)
|
||||||
|
|
||||||
def _build_crank(
|
def _build_crank(
|
||||||
self, limit: Decimal = Decimal(32), add_self: bool = False
|
self, limit: Decimal = Decimal(32), add_self: bool = False
|
||||||
|
|
|
@ -99,7 +99,9 @@ class SimpleMarketMaker:
|
||||||
price, inventory
|
price, inventory
|
||||||
)
|
)
|
||||||
|
|
||||||
current_orders = self.market_operations.load_my_orders()
|
current_orders = self.market_operations.load_my_orders(
|
||||||
|
include_expired=True
|
||||||
|
)
|
||||||
buy_orders = [
|
buy_orders = [
|
||||||
order for order in current_orders if order.side == mango.Side.BUY
|
order for order in current_orders if order.side == mango.Side.BUY
|
||||||
]
|
]
|
||||||
|
@ -145,7 +147,7 @@ class SimpleMarketMaker:
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
self._logger.info("Cleaning up.")
|
self._logger.info("Cleaning up.")
|
||||||
orders = self.market_operations.load_my_orders()
|
orders = self.market_operations.load_my_orders(include_expired=True)
|
||||||
for order in orders:
|
for order in orders:
|
||||||
self.market_operations.cancel_order(order)
|
self.market_operations.cancel_order(order)
|
||||||
|
|
||||||
|
@ -228,9 +230,14 @@ class SimpleMarketMaker:
|
||||||
|
|
||||||
return len(orders) == 0 or not all(
|
return len(orders) == 0 or not all(
|
||||||
[
|
[
|
||||||
(within_tolerance(price, order.price, self.existing_order_tolerance))
|
(
|
||||||
and within_tolerance(
|
not order.expired
|
||||||
quantity, order.quantity, self.existing_order_tolerance
|
and within_tolerance(
|
||||||
|
price, order.price, self.existing_order_tolerance
|
||||||
|
)
|
||||||
|
and within_tolerance(
|
||||||
|
quantity, order.quantity, self.existing_order_tolerance
|
||||||
|
)
|
||||||
)
|
)
|
||||||
for order in orders
|
for order in orders
|
||||||
]
|
]
|
||||||
|
|
|
@ -355,12 +355,14 @@ class SpotMarketOperations(MarketOperations):
|
||||||
def load_orderbook(self) -> OrderBook:
|
def load_orderbook(self) -> OrderBook:
|
||||||
return self.spot_market.fetch_orderbook(self.context)
|
return self.spot_market.fetch_orderbook(self.context)
|
||||||
|
|
||||||
def load_my_orders(self) -> typing.Sequence[Order]:
|
def load_my_orders(self, include_expired: bool = False) -> typing.Sequence[Order]:
|
||||||
if not self.open_orders_address:
|
if not self.open_orders_address:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
orderbook: OrderBook = self.load_orderbook()
|
orderbook: OrderBook = self.load_orderbook()
|
||||||
return orderbook.all_orders_for_owner(self.open_orders_address)
|
return orderbook.all_orders_for_owner(
|
||||||
|
self.open_orders_address, include_expired=include_expired
|
||||||
|
)
|
||||||
|
|
||||||
def _build_crank(
|
def _build_crank(
|
||||||
self, limit: Decimal = Decimal(32), add_self: bool = False
|
self, limit: Decimal = Decimal(32), add_self: bool = False
|
||||||
|
|
|
@ -18,7 +18,6 @@ import logging
|
||||||
import typing
|
import typing
|
||||||
import websocket
|
import websocket
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from rx.subject.behaviorsubject import BehaviorSubject
|
from rx.subject.behaviorsubject import BehaviorSubject
|
||||||
from rx.core.typing import Disposable
|
from rx.core.typing import Disposable
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
|
@ -26,6 +25,7 @@ from solana.rpc.types import RPCResponse
|
||||||
|
|
||||||
from .accountinfo import AccountInfo
|
from .accountinfo import AccountInfo
|
||||||
from .context import Context
|
from .context import Context
|
||||||
|
from .datetimes import local_now
|
||||||
from .observables import EventSource
|
from .observables import EventSource
|
||||||
from .reconnectingwebsocket import ReconnectingWebsocket
|
from .reconnectingwebsocket import ReconnectingWebsocket
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class WebSocketSubscription(
|
||||||
TSubscriptionInstance
|
TSubscriptionInstance
|
||||||
]()
|
]()
|
||||||
self.ws: typing.Optional[ReconnectingWebsocket] = None
|
self.ws: typing.Optional[ReconnectingWebsocket] = None
|
||||||
self.pong: BehaviorSubject = BehaviorSubject(datetime.now())
|
self.pong: BehaviorSubject = BehaviorSubject(local_now())
|
||||||
self._pong_subscription: typing.Optional[Disposable] = None
|
self._pong_subscription: typing.Optional[Disposable] = None
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
@ -309,7 +309,7 @@ class SharedWebSocketSubscriptionManager(WebSocketSubscriptionManager):
|
||||||
def __init__(self, context: Context, ping_interval: int = 10) -> None:
|
def __init__(self, context: Context, ping_interval: int = 10) -> None:
|
||||||
super().__init__(context, ping_interval)
|
super().__init__(context, ping_interval)
|
||||||
self.ws: typing.Optional[ReconnectingWebsocket] = None
|
self.ws: typing.Optional[ReconnectingWebsocket] = None
|
||||||
self.pong: BehaviorSubject = BehaviorSubject(datetime.now())
|
self.pong: BehaviorSubject = BehaviorSubject(local_now())
|
||||||
self._pong_subscription: typing.Optional[Disposable] = None
|
self._pong_subscription: typing.Optional[Disposable] = None
|
||||||
|
|
||||||
def open(self) -> None:
|
def open(self) -> None:
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import construct
|
import construct
|
||||||
import datetime
|
|
||||||
import mango
|
import mango
|
||||||
import mango.marketmaking
|
import mango.marketmaking
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from mango.lotsizeconverter import NullLotSizeConverter
|
|
||||||
from pyserum.market.market import Market as PySerumMarket
|
from pyserum.market.market import Market as PySerumMarket
|
||||||
from pyserum.market.state import MarketState as PySerumMarketState
|
from pyserum.market.state import MarketState as PySerumMarketState
|
||||||
from solana.keypair import Keypair
|
from solana.keypair import Keypair
|
||||||
|
@ -250,7 +248,7 @@ def fake_price(
|
||||||
mango.OracleSource(
|
mango.OracleSource(
|
||||||
"test", "test", mango.SupportedOracleFeature.TOP_BID_AND_OFFER, market
|
"test", "test", mango.SupportedOracleFeature.TOP_BID_AND_OFFER, market
|
||||||
),
|
),
|
||||||
datetime.datetime.now(),
|
mango.utc_now(),
|
||||||
market,
|
market,
|
||||||
bid,
|
bid,
|
||||||
price,
|
price,
|
||||||
|
@ -337,7 +335,7 @@ def fake_root_bank() -> mango.RootBank:
|
||||||
[],
|
[],
|
||||||
Decimal(0),
|
Decimal(0),
|
||||||
Decimal(0),
|
Decimal(0),
|
||||||
datetime.datetime.now(),
|
mango.utc_now(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -347,7 +345,11 @@ def fake_cache() -> mango.Cache:
|
||||||
|
|
||||||
|
|
||||||
def fake_root_bank_cache() -> mango.RootBankCache:
|
def fake_root_bank_cache() -> mango.RootBankCache:
|
||||||
return mango.RootBankCache(Decimal(1), Decimal(2), datetime.datetime.now())
|
return mango.RootBankCache(
|
||||||
|
Decimal(1),
|
||||||
|
Decimal(2),
|
||||||
|
mango.utc_now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def fake_group(address: typing.Optional[PublicKey] = None) -> mango.Group:
|
def fake_group(address: typing.Optional[PublicKey] = None) -> mango.Group:
|
||||||
|
@ -467,7 +469,7 @@ def fake_model_state(
|
||||||
placed_orders_container = placed_orders_container or fake_placed_orders_container()
|
placed_orders_container = placed_orders_container or fake_placed_orders_container()
|
||||||
inventory = inventory or fake_inventory()
|
inventory = inventory or fake_inventory()
|
||||||
orderbook = orderbook or mango.OrderBook(
|
orderbook = orderbook or mango.OrderBook(
|
||||||
"FAKE", NullLotSizeConverter(), fake_bids(), fake_asks()
|
"FAKE", mango.NullLotSizeConverter(), fake_bids(), fake_asks()
|
||||||
)
|
)
|
||||||
event_queue = event_queue or mango.NullEventQueue()
|
event_queue = event_queue or mango.NullEventQueue()
|
||||||
group_watcher: mango.ManualUpdateWatcher[mango.Group] = mango.ManualUpdateWatcher(
|
group_watcher: mango.ManualUpdateWatcher[mango.Group] = mango.ManualUpdateWatcher(
|
||||||
|
|
|
@ -3,7 +3,7 @@ import argparse
|
||||||
from ...context import mango
|
from ...context import mango
|
||||||
from ...fakes import fake_context, fake_model_state, fake_price
|
from ...fakes import fake_context, fake_model_state, fake_price
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from mango.marketmaking.orderchain.ratioselement import RatiosElement
|
from mango.marketmaking.orderchain.ratioselement import RatiosElement
|
||||||
|
@ -45,8 +45,8 @@ def test_uses_specified_order_parameters() -> None:
|
||||||
)
|
)
|
||||||
result = actual.process(context, model_state, [])
|
result = actual.process(context, model_state, [])
|
||||||
|
|
||||||
assert result[0].expiration > datetime.now() - timedelta(seconds=1)
|
assert result[0].expiration > mango.utc_now() - timedelta(seconds=1)
|
||||||
assert result[0].expiration < datetime.now() + timedelta(seconds=5)
|
assert result[0].expiration < mango.utc_now() + timedelta(seconds=5)
|
||||||
assert result[0].match_limit == 15
|
assert result[0].match_limit == 15
|
||||||
assert result[0].order_type == mango.OrderType.POST_ONLY_SLIDE
|
assert result[0].order_type == mango.OrderType.POST_ONLY_SLIDE
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import mango
|
import mango
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from mango.marketmaking.toleranceorderreconciler import ToleranceOrderReconciler
|
from mango.marketmaking.toleranceorderreconciler import ToleranceOrderReconciler
|
||||||
|
|
||||||
|
@ -214,7 +214,7 @@ def test_price_outside_negative_tolerance_no_match() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_time_in_force_early_match() -> None:
|
def test_time_in_force_early_match() -> None:
|
||||||
now = datetime.now()
|
now = mango.utc_now()
|
||||||
actual = ToleranceOrderReconciler(
|
actual = ToleranceOrderReconciler(
|
||||||
Decimal("0.001"), Decimal(0), timedelta(seconds=5)
|
Decimal("0.001"), Decimal(0), timedelta(seconds=5)
|
||||||
)
|
)
|
||||||
|
@ -232,7 +232,7 @@ def test_time_in_force_early_match() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_time_in_force_too_early_no_match() -> None:
|
def test_time_in_force_too_early_no_match() -> None:
|
||||||
now = datetime.now()
|
now = mango.utc_now()
|
||||||
actual = ToleranceOrderReconciler(
|
actual = ToleranceOrderReconciler(
|
||||||
Decimal("0.001"), Decimal(0), timedelta(seconds=5)
|
Decimal("0.001"), Decimal(0), timedelta(seconds=5)
|
||||||
)
|
)
|
||||||
|
@ -250,7 +250,7 @@ def test_time_in_force_too_early_no_match() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_time_in_force_late_match() -> None:
|
def test_time_in_force_late_match() -> None:
|
||||||
now = datetime.now()
|
now = mango.utc_now()
|
||||||
actual = ToleranceOrderReconciler(
|
actual = ToleranceOrderReconciler(
|
||||||
Decimal("0.001"), Decimal(0), timedelta(seconds=5)
|
Decimal("0.001"), Decimal(0), timedelta(seconds=5)
|
||||||
)
|
)
|
||||||
|
@ -268,7 +268,7 @@ def test_time_in_force_late_match() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_time_in_force_too_late_no_match() -> None:
|
def test_time_in_force_too_late_no_match() -> None:
|
||||||
now = datetime.now()
|
now = mango.utc_now()
|
||||||
actual = ToleranceOrderReconciler(
|
actual = ToleranceOrderReconciler(
|
||||||
Decimal("0.001"), Decimal(0), timedelta(seconds=5)
|
Decimal("0.001"), Decimal(0), timedelta(seconds=5)
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,6 @@ from .context import mango
|
||||||
from .fakes import fake_account_info, fake_seeded_public_key
|
from .fakes import fake_account_info, fake_seeded_public_key
|
||||||
from .data import load_cache
|
from .data import load_cache
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ def test_cache_constructor() -> None:
|
||||||
meta_data = mango.Metadata(
|
meta_data = mango.Metadata(
|
||||||
mango.layouts.DATA_TYPE.parse(bytearray(b"\x07")), mango.Version.V1, True
|
mango.layouts.DATA_TYPE.parse(bytearray(b"\x07")), mango.Version.V1, True
|
||||||
)
|
)
|
||||||
timestamp = datetime.now()
|
timestamp = mango.utc_now()
|
||||||
price_cache = [mango.PriceCache(Decimal(26), timestamp)]
|
price_cache = [mango.PriceCache(Decimal(26), timestamp)]
|
||||||
root_bank_cache = [
|
root_bank_cache = [
|
||||||
mango.RootBankCache(Decimal("0.00001"), Decimal("0.00001"), timestamp)
|
mango.RootBankCache(Decimal("0.00001"), Decimal("0.00001"), timestamp)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
from .context import mango
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
|
def test_order_expired() -> None:
|
||||||
|
actual = mango.Order.from_basic_info(
|
||||||
|
side=mango.Side.BUY,
|
||||||
|
price=Decimal(10),
|
||||||
|
quantity=Decimal(20),
|
||||||
|
order_type=mango.OrderType.LIMIT,
|
||||||
|
expiration=mango.utc_now() - timedelta(seconds=1),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert actual.expired
|
||||||
|
|
||||||
|
|
||||||
|
def test_order_not_expired() -> None:
|
||||||
|
actual = mango.Order.from_basic_info(
|
||||||
|
side=mango.Side.BUY,
|
||||||
|
price=Decimal(10),
|
||||||
|
quantity=Decimal(20),
|
||||||
|
order_type=mango.OrderType.LIMIT,
|
||||||
|
expiration=mango.utc_now() + timedelta(seconds=5),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not actual.expired
|
|
@ -5,7 +5,6 @@ from solana.publickey import PublicKey
|
||||||
from .context import mango
|
from .context import mango
|
||||||
from .fakes import fake_account_info, fake_seeded_public_key
|
from .fakes import fake_account_info, fake_seeded_public_key
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,7 +84,7 @@ class TstFillPE(mango.PerpFillEvent):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
0,
|
0,
|
||||||
Decimal(0),
|
Decimal(0),
|
||||||
datetime.now(),
|
mango.utc_now(),
|
||||||
mango.Side.BUY,
|
mango.Side.BUY,
|
||||||
Decimal(1),
|
Decimal(1),
|
||||||
Decimal(1),
|
Decimal(1),
|
||||||
|
|
|
@ -39,7 +39,7 @@ def test_root_bank_constructor() -> None:
|
||||||
node_bank = fake_seeded_public_key("node bank")
|
node_bank = fake_seeded_public_key("node bank")
|
||||||
deposit_index = Decimal(98765)
|
deposit_index = Decimal(98765)
|
||||||
borrow_index = Decimal(12345)
|
borrow_index = Decimal(12345)
|
||||||
timestamp = datetime.now()
|
timestamp = mango.utc_now()
|
||||||
|
|
||||||
actual = mango.RootBank(
|
actual = mango.RootBank(
|
||||||
account_info,
|
account_info,
|
||||||
|
|
|
@ -26,7 +26,7 @@ def test_transaction_instruction_constructor() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_transaction_scout_constructor() -> None:
|
def test_transaction_scout_constructor() -> None:
|
||||||
timestamp: datetime = datetime.now()
|
timestamp: datetime = mango.utc_now()
|
||||||
signatures: typing.Sequence[str] = ["Signature1", "Signature2"]
|
signatures: typing.Sequence[str] = ["Signature1", "Signature2"]
|
||||||
succeeded: bool = True
|
succeeded: bool = True
|
||||||
group_name: str = "BTC_ETH_USDT"
|
group_name: str = "BTC_ETH_USDT"
|
||||||
|
|
Loading…
Reference in New Issue