OrderBook can now provide expired orders, if requested.

This commit is contained in:
Geoff Taylor 2022-02-22 12:06:20 +00:00
parent c3f7ad60f6
commit 906b419fc6
36 changed files with 251 additions and 137 deletions

View File

@ -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))
``` ```

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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.")

View File

@ -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

View File

@ -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

37
mango/datetimes.py Normal file
View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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,

View File

@ -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]:

View File

@ -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}'

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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(

View File

@ -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,

View File

@ -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]]

View File

@ -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])

View File

@ -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

View File

@ -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

View File

@ -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())

View File

@ -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

View File

@ -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
] ]

View File

@ -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

View File

@ -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:

View File

@ -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(

View File

@ -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

View File

@ -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)
) )

View File

@ -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)

28
tests/test_order.py Normal file
View File

@ -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

View File

@ -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),

View File

@ -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,

View File

@ -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"