Beispiel #1
0
    def bid(self, price: Dec, quantity: Dec,
            agent: "ag.MarketPlayer") -> Optional[Bid]:
        """
        Submit a new sell order to the book.
        """
        quantity = HavvenManager.round_decimal(quantity)

        # Disallow empty orders.
        if quantity == Dec(0):
            return None

        # Compute the fee to be paid.
        fee = self.buyer_fee(price, quantity)

        # Fail if the value of the order exceeds the agent's available supply.
        agent.round_values()
        if agent.__getattribute__(
                f"available_{self.quoted}") < HavvenManager.round_decimal(
                    price * quantity) + fee:
            return None

        bid = Bid(price, quantity, fee, agent, self)

        # Attempt to trade the bid immediately.
        if self.continuous_order_matching:
            self.match()

        return bid
    def step(self) -> None:
        # keep all havvens escrowed to make issuing nomins easier
        if self.available_havvens > 0:
            self.escrow_havvens(self.available_havvens)

        nomins = self.available_nomins + self.remaining_issuance_rights()

        if nomins > 0:
            trade = self._find_best_nom_fiat_trade()
            last_price = 0
            while trade is not None and HavvenManager.round_decimal(nomins) > 0:
                if last_price == trade[0]:
                    break
                last_price = trade[0]
                self._issue_nomins_up_to(trade[1])
                ask = self._make_nom_fiat_trade(trade)
                trade = self._find_best_nom_fiat_trade()
                nomins = self.available_nomins + self.remaining_issuance_rights()

        if self.available_fiat > 0:
            trade = self._find_best_fiat_nom_trade()
            last_price = 0
            while trade is not None and HavvenManager.round_decimal(self.available_fiat) > 0:
                if last_price == trade[0]:
                    break
                last_price = trade[0]
                bid = self._make_fiat_nom_trade(trade)
                trade = self._find_best_fiat_nom_trade()

        if self.issued_nomins:
            if self.available_nomins < self.issued_nomins:
                self.burn_nomins(self.available_nomins)
            else:
                self.burn_nomins(self.issued_nomins)
 def round_values(self) -> None:
     """
     Apply rounding to this player's nomin, fiat, havven values.
     """
     self.nomins = hm.round_decimal(self.nomins)
     self.fiat = hm.round_decimal(self.fiat)
     self.havvens = hm.round_decimal(self.havvens)
 def profit_fraction(self) -> Dec:
     """
     Return profit accrued as a fraction of initial wealth.
     May be negative.
     """
     if hm.round_decimal(self.initial_wealth) != 0:
         return hm.round_decimal(self.profit() / self.initial_wealth)
     else:
         return Dec('0')
Beispiel #5
0
    def step(self) -> None:
        if self.nomin_havven_order is not None:
            if self.model.manager.time >= self.nomin_havven_order[
                    0] + self.trade_duration:
                self.nomin_havven_order[1].cancel()
                self.nomin_havven_order = None
        if self.nomin_fiat_order is not None:
            if self.model.manager.time >= self.nomin_fiat_order[
                    0] + self.trade_duration:
                self.nomin_fiat_order[1].cancel()
                self.nomin_fiat_order = None
        if self.fiat_havven_order is not None:
            if self.model.manager.time >= self.fiat_havven_order[
                    0] + self.trade_duration:
                self.fiat_havven_order[1].cancel()
                self.fiat_havven_order = None

        if self.available_nomins > 0:
            if len(self.model.datacollector.model_vars['0']) > 0:
                havven_supply = self.model.datacollector.model_vars[
                    'Havven Supply'][-1]
                fiat_supply = self.model.datacollector.model_vars[
                    'Fiat Supply'][-1]
                # buy into the market with more supply, as by virtue of there being more supply,
                # the market will probably have a better price...
                if havven_supply > fiat_supply:
                    order = self.place_havven_nomin_bid_with_fee(
                        self.available_nomins * self.sell_rate,
                        self.havven_nomin_market.price *
                        (Dec(1) - self.trade_premium))
                    if order is None:
                        return
                    self.nomin_havven_order = (self.model.manager.time, order)

                else:
                    order = self.place_nomin_fiat_ask_with_fee(
                        self.available_nomins * self.sell_rate,
                        self.nomin_fiat_market.price *
                        (Dec(1) + self.trade_premium))
                    if order is None:
                        return
                    self.nomin_fiat_order = (self.model.manager.time, order)

        if self.available_fiat > 0 and not self.fiat_havven_order:
            order = self.place_havven_fiat_bid_with_fee(
                hm.round_decimal(self.available_fiat * self.sell_rate),
                self.havven_fiat_market.price * (Dec(1) - self.trade_premium))
            if order is None:
                return
            self.fiat_havven_order = (self.model.manager.time, order)

        if self.available_havvens > 0:
            self.escrow_havvens(self.available_havvens)

        issuable = self.max_issuance_rights() - self.issued_nomins
        if hm.round_decimal(issuable) > 0:
            self.issue_nomins(issuable)
 def _fraction(
     self, qty: Dec, divisor: Dec = Dec(3), minimum: Dec = Dec(1)) -> Dec:
     """
     Return a fraction of the given quantity, with a minimum.
     Used for depleting reserves gradually.
     """
     return max(hm.round_decimal(qty / divisor), min(minimum, qty))
 def _reverse_multiple_no_fees(self) -> Dec:
     """
     The value multiple after one reverse arbitrage cycle, neglecting fees.
     """
     # hav -> nom -> fiat -> hav
     return hm.round_decimal((self.havven_nomin_market.highest_bid_price() *
                              self.nomin_fiat_market.highest_bid_price()) / \
                             self.havven_fiat_market.lowest_ask_price())
 def _sell_quoted_with_fee(self, book: "ob.OrderBook",
                           quantity: Dec) -> Optional["ob.Bid"]:
     """
     Sell a quantity of the quoted currency into the given market, including the
     fee, as calculated by the provided function.
     """
     price = book.lowest_ask_price()
     return book.buy(
         hm.round_decimal(book.quoted_qty_rcvd(quantity) / price), self)
Beispiel #9
0
 def bids_not_lower_quoted_quantity(self,
                                    price: Dec,
                                    base_capital: Optional[Dec] = None
                                    ) -> Dec:
     """
     Return the quantity of quoted currency you would obtain offering no less
     than a certain price, if you could spend up to a quantity of the base currency.
     """
     bought = Dec(0)
     sold = Dec(0)
     for bid in self.bids_not_lower(price):
         if base_capital is not None and sold + bid.quantity > base_capital:
             bought += HavvenManager.round_decimal(
                 (base_capital - sold) * bid.price)
             break
         sold += bid.quantity
         bought += HavvenManager.round_decimal(bid.price * bid.quantity)
     return bought
Beispiel #10
0
 def asks_not_higher_base_quantity(self,
                                   price: Dec,
                                   quoted_capital: Optional[Dec] = None
                                   ) -> Dec:
     """
     Return the quantity of base currency you would obtain offering no more
     than a certain price, if you could spend up to a quantity of the quoted currency.
     """
     bought = Dec(0)
     sold = Dec(0)
     for ask in self.asks_not_higher(price):
         next_sold = HavvenManager.round_decimal(ask.price * ask.quantity)
         if quoted_capital is not None and sold + next_sold > quoted_capital:
             bought += HavvenManager.round_decimal(
                 ask.quantity * (quoted_capital - sold) / next_sold)
             break
         sold += next_sold
         bought += ask.quantity
     return bought
Beispiel #11
0
 def __init__(self, *args, **kwargs) -> None:
     super().__init__(*args, **kwargs)
     self.fiat_havven_order: Optional[Tuple[int, "ob.Bid"]] = None
     """The time the order was placed as well as the fiat/hvn order"""
     self.nomin_havven_order: Optional[Tuple[int, "ob.Bid"]] = None
     self.nomin_fiat_order: Optional[Tuple[int, "ob.Ask"]] = None
     self.sell_rate: Dec = hm.round_decimal(Dec(random.random() / 3 + 0.1))
     self.trade_premium: Dec = Dec('0.01')
     self.trade_duration: int = 10
     # step when initialised so nomins appear on the market.
     self.step()
    def _sell_quoted(self, book: "ob.OrderBook",
                     quantity: Dec) -> Optional["ob.Bid"]:
        """
        Sell a quantity of the quoted currency into the given market.
        """
        remaining_quoted = self.__getattribute__(f"available_{book.quoted}")
        quantity = min(quantity, remaining_quoted)
        if quantity < Dec(
                '0.0005'
        ):  # TODO: remove workaround, and/or factor into epsilon variable
            return None

        next_qty = hm.round_decimal(
            min(quantity, book.lowest_ask_quantity()) /
            book.lowest_ask_price())
        pre_sold = self.__getattribute__(f"available_{book.quoted}")
        bid = book.buy(next_qty, self)
        total_sold = pre_sold - self.__getattribute__(
            f"available_{book.quoted}")

        # Keep on bidding until we either run out of reserves or sellers, or we've bought enough.
        while bid is not None and not bid.active and total_sold < quantity and len(
                book.asks) == 0:
            next_qty = hm.round_decimal(
                min(quantity - total_sold, book.lowest_ask_quantity()) /
                book.lowest_ask_price())
            pre_sold = self.__getattribute__(f"available_{book.quoted}")
            bid = book.buy(next_qty, self)
            total_sold += pre_sold - self.__getattribute__(
                f"available_{book.quoted}")

        if total_sold < quantity:
            if bid is not None:
                bid.cancel()
            price = book.lowest_ask_price()
            bid = book.bid(price,
                           hm.round_decimal((quantity - total_sold) / price),
                           self)

        return bid
Beispiel #13
0
    def sell(self, quantity: Dec, agent: "ag.MarketPlayer") -> Optional[Ask]:
        """
        Sell a quantity of the base currency at the best available price.
        """
        price = HavvenManager.round_decimal(
            self.price_to_sell_quantity(quantity))
        ask = self.ask(price, quantity, agent)

        # cancel the ask if it isn't filled immediately, as a market buy/sell should
        # always be filled (unless the market dries up)
        if not self.continuous_order_matching and ask:
            ask.cancel()
        return ask
Beispiel #14
0
    def buy(self, quantity: Dec, agent: "ag.MarketPlayer") -> Optional[Bid]:
        """
        Buy a quantity of the base currency at the best available price.
        """
        price = HavvenManager.round_decimal(
            self.price_to_buy_quantity(quantity))
        bid = self.bid(price, quantity, agent)

        # cancel the bid if it isn't filled immediately, as a market buy/sell should
        # always be filled (unless the market dries up)
        if not self.continuous_order_matching and bid:
            bid.cancel()
        return bid
    def _issue_nomins_up_to(self, quantity: Dec) -> bool:
        """
        If quantity > currently issued nomins, including fees to trade, issue more nomins

        If the player cant issue more nomins than the quantity,
        """
        fee = HavvenManager.round_decimal(self.model.fee_manager.transferred_nomins_fee(quantity))

        # if there are enough nomins, return
        if self.available_nomins > fee + quantity:
            return True

        nomins_needed = fee + quantity - self.available_nomins

        if self.remaining_issuance_rights() > nomins_needed:
            return self.issue_nomins(nomins_needed)
        else:
            return self.issue_nomins(self.remaining_issuance_rights())
Beispiel #16
0
    def __init__(self, price: Dec, time: int, quantity: Dec, fee: Dec,
                 issuer: "ag.MarketPlayer", book: "OrderBook") -> None:
        self.price = HavvenManager.round_decimal(price)
        """Quoted currency per unit of base currency."""

        self.fee = fee
        """An extra fee charged on top of the face value of the order."""

        self.time = time
        """The time this order was created, or last modified."""

        self.quantity = quantity
        """Denominated in the base currency."""

        self.issuer = issuer
        """The player which issued this order."""

        self.book = book
        """The order book this order is listed on."""

        self.active = self.quantity > 0
        """Whether the order is actively listed or not."""
Beispiel #17
0
    def __init__(self, model_settings: Dict[str, Any],
                 fee_settings: Dict[str, Any], agent_settings: Dict[str, Any],
                 havven_settings: Dict[str, Any]) -> None:
        """

        :param model_settings: Setting that are modifiable on the frontend
         - agent_fraction: what percentage of each agent to use
         - num_agents: the total number of agents to use
         - utilisation_ratio_max: the max utilisation ratio for nomin issuance against havvens
         - continuous_order_matching: whether to match orders as they come,
         or at the end of each tick
        :param fee_settings: explained in feemanager.py
        :param agent_settings: explained in agentmanager.py
        :param havven_settings: explained in havvenmanager.py
        """
        agent_fractions = model_settings['agent_fractions']
        num_agents = model_settings['num_agents']
        utilisation_ratio_max = model_settings['utilisation_ratio_max']
        continuous_order_matching = model_settings['continuous_order_matching']

        # Mesa setup.
        super().__init__()

        # The schedule will activate agents in a random order per step.
        self.schedule = RandomActivation(self)

        # Set up data collection.
        self.datacollector = stats.create_datacollector()

        # Initialise simulation managers.
        self.manager = HavvenManager(Dec(utilisation_ratio_max),
                                     continuous_order_matching,
                                     havven_settings)
        self.fee_manager = FeeManager(self.manager, fee_settings)
        self.market_manager = MarketManager(self.manager, self.fee_manager)
        self.mint = Mint(self.manager, self.market_manager)

        self.agent_manager = AgentManager(self, num_agents, agent_fractions,
                                          agent_settings)
    def step(self) -> None:
        """
        Find an exploitable arbitrage cycle.

        The only cycles that exist are HAV -> FIAT -> NOM -> HAV,
        its rotations, and the reverse cycles.
        This bot will consider those and act to exploit the most
        favourable such cycle if the profit available around that
        cycle is better than the profit threshold (including fees).
        """

        self.havven_fiat_bid_qty = self.havven_fiat_market.highest_bid_quantity(
        )
        self.havven_nomin_bid_qty = self.havven_nomin_market.highest_bid_quantity(
        )
        self.nomin_fiat_bid_qty = self.nomin_fiat_market.highest_bid_quantity()
        self.nomin_fiat_ask_qty = hm.round_decimal(
            self.nomin_fiat_market.lowest_ask_quantity() *
            self.nomin_fiat_market.lowest_ask_price())
        self.havven_nomin_ask_qty = hm.round_decimal(
            self.havven_nomin_market.lowest_ask_quantity() *
            self.havven_nomin_market.lowest_ask_price())
        self.havven_fiat_ask_qty = hm.round_decimal(
            self.havven_fiat_market.lowest_ask_quantity() *
            self.havven_fiat_market.lowest_ask_price())

        wealth = self.wealth()

        # Consider the forward direction
        cc_net_wealth = self.model.fiat_value(
            **self.forward_havven_cycle_balances()) - wealth
        nn_net_wealth = self.model.fiat_value(
            **self.forward_nomin_cycle_balances()) - wealth
        ff_net_wealth = self.model.fiat_value(
            **self.forward_fiat_cycle_balances()) - wealth
        max_net_wealth = max(cc_net_wealth, nn_net_wealth, ff_net_wealth)

        if max_net_wealth > self.profit_threshold:
            if cc_net_wealth == max_net_wealth:
                self.forward_havven_cycle_trade()
            elif nn_net_wealth == max_net_wealth:
                self.forward_nomin_cycle_trade()
            else:
                self.forward_fiat_cycle_trade()
            return

        # Now the reverse direction
        cc_net_wealth = self.model.fiat_value(
            **self.reverse_havven_cycle_balances()) - wealth
        nn_net_wealth = self.model.fiat_value(
            **self.reverse_nomin_cycle_balances()) - wealth
        ff_net_wealth = self.model.fiat_value(
            **self.reverse_fiat_cycle_balances()) - wealth
        max_net_wealth = max(cc_net_wealth, nn_net_wealth, ff_net_wealth)

        if max_net_wealth > self.profit_threshold:
            if cc_net_wealth == max_net_wealth:
                self.reverse_havven_cycle_trade()
            elif nn_net_wealth == max_net_wealth:
                self.reverse_nomin_cycle_trade()
            else:
                self.reverse_fiat_cycle_trade()
 def _reverse_multiple(self) -> Dec:
     """The return after one reverse arbitrage cycle."""
     # As above. If the fees were not just levied as percentages this would need to be updated.
     return hm.round_decimal(self._reverse_multiple_no_fees() /
                             self._cycle_fee_rate())
 def _forward_multiple(self) -> Dec:
     """The return after one forward arbitrage cycle."""
     # Note, this only works because the fees are purely multiplicative.
     return hm.round_decimal(self._forward_multiple_no_fees() /
                             self._cycle_fee_rate())
Beispiel #21
0
 def seller_received_quantity(self, price: Dec, quantity: Dec) -> Dec:
     """
     The quantity of the quoted currency received by a seller (fees deducted).
     """
     return self.quoted_qty_rcvd(
         HavvenManager.round_decimal(price * quantity))
 def _cycle_fee_rate(self) -> Dec:
     """Divide by this fee rate to determine losses after one traversal of an arbitrage cycle."""
     return hm.round_decimal((Dec(1) + self.model.fee_manager.nomin_fee_rate) * \
                             (Dec(1) + self.model.fee_manager.havven_fee_rate) * \
                             (Dec(1) + self.model.fee_manager.fiat_fee_rate))
Beispiel #23
0
 def _havven_nomin_ask(self) -> "ob.Ask":
     price = self.havven_nomin_market.price
     movement = hm.round_decimal(
         Dec(2 * random.random() - 1) * price * self.variance)
     return self.place_havven_nomin_ask(
         self._fraction(self.available_havvens, Dec(10)), price + movement)
Beispiel #24
0
 def _nomin_fiat_bid(self) -> "ob.Bid":
     price = self.nomin_fiat_market.price
     movement = hm.round_decimal(
         Dec(2 * random.random() - 1) * price * self.variance)
     return self.place_nomin_fiat_bid(
         self._fraction(self.available_fiat, Dec(10)), price + movement)
Beispiel #25
0
    def update_ask(self,
                   ask: Ask,
                   new_price: Dec,
                   new_quantity: Dec,
                   fee: Optional[Dec] = None) -> None:
        """
        Update an Ask's details in the book, recomputing fees, cached quantities,
        and the user's unavailable currency totals.
        If fee is not None, then update the fee directly, rather than recomputing it.
        """
        # Do nothing if the order is inactive.
        if not ask.active:
            return

        new_price = HavvenManager.round_decimal(new_price)
        new_quantity = HavvenManager.round_decimal(new_quantity)
        if fee is not None:
            fee = HavvenManager.round_decimal(fee)

        # Do nothing if the price and quantity would remain unchanged.
        if ask.price == new_price and ask.quantity == new_quantity:
            if fee is None or fee == ask.fee:
                return
            else:
                print(ask)
                raise Exception(
                    "Fee changed, but price and quantity are unchanged...")

        # If the ask is updated with a non-positive quantity, it is cancelled.
        if new_quantity <= 0:
            self.cancel_ask(ask)
            return

        # Compute the new fee
        new_fee = fee
        if fee is None:
            new_fee = self.seller_fee(new_price, new_quantity)

        # Update the unavailable quantities for this ask,
        # deducting the old and crediting the new.
        ask.issuer.__dict__[f"unavailable_{self.base}"] += \
            (new_quantity + new_fee) - (ask.quantity + ask.fee)

        if ask.price == new_price:
            # We may assume the current price is already recorded,
            # so no need to call _ask_bucket_add_ which checks before
            # inserting. Something is wrong if the key is not found.
            self.ask_price_buckets[new_price] += (new_quantity - ask.quantity)

            # As the price is unchanged, order book position need not be
            # updated, just set the quantity and fee.
            ask.quantity = new_quantity
            ask.fee = new_fee
        else:
            # Deduct the old quantity from its price bucket,
            # and add the new quantity into the appropriate bucket.
            self._ask_bucket_deduct(ask.price, ask.quantity)
            self._ask_bucket_add(new_price, new_quantity)

            # Since the price changed, update the ask's position
            # in the book.
            self.asks.remove(ask)
            ask.price = new_price
            ask.quantity = new_quantity
            ask.fee = new_fee
            # Only set the timestep if the price was updated.
            ask.time = self.time
            self.asks.add(ask)

        # Advance time.
        self.step()
Beispiel #26
0
 def setup(self, init_value: Dec):
     endowment = hm.round_decimal(init_value * Dec(4))
     self.fiat = init_value
     self.model.endow_havvens(self, endowment)
Beispiel #27
0
 def buyer_fee(self, price: Dec, quantity: Dec) -> Dec:
     """
     Return the fee paid on the quoted end (by the buyer) for a bid
     of the given quantity and price.
     """
     return self.quoted_fee(HavvenManager.round_decimal(price * quantity))