def test_match_recommendations_fake_offer_bid(self, market,
                                                  two_sided_market_matching):
        """Test the case when an offer or bid which don't belong to market is sent."""
        bid = Bid("bid_id1", pendulum.now(), price=2, energy=1, buyer="B")
        offer = Offer("id", pendulum.now(), price=2, energy=1, seller="other")

        market.bids = {"bid_id1": bid}

        recommendations = [
            BidOfferMatch(bids=[bid.serializable_dict()],
                          offers=[offer.serializable_dict()],
                          trade_rate=2,
                          selected_energy=1,
                          market_id=market.id).serializable_dict()
        ]
        # The sent offer isn't in market offers, should be skipped
        market.match_recommendations(recommendations)
        assert len(market.trades) == 0
        assert not market.validate_bid_offer_match.called
        assert not market.accept_bid_offer_pair.called
        assert not market._replace_offers_bids_with_residual_in_recommendations_list.called

        market.offers = {offer.id: offer}
        market.bids = {}
        # The sent bid isn't in market offers, should be skipped
        market.match_recommendations(recommendations)
        assert len(market.trades) == 0
        assert not market.validate_bid_offer_match.called
        assert not market.accept_bid_offer_pair.called
        assert not market._replace_offers_bids_with_residual_in_recommendations_list.called
    def test_match_recommendations_one_offer_multiple_bids(self, market):
        """Test match_recommendations() method of TwoSidedMarket using 1 offer N bids."""
        bid1 = Bid("bid_id1", pendulum.now(), price=1, energy=1, buyer="Buyer")
        bid2 = Bid("bid_id2", pendulum.now(), price=1, energy=1, buyer="Buyer")
        offer1 = Offer("offer_id1",
                       pendulum.now(),
                       price=2,
                       energy=2,
                       seller="Seller")

        market.bids = {"bid_id1": bid1, "bid_id2": bid2}
        market.offers = {"offer_id1": offer1}

        recommendations = [
            BidOfferMatch(
                bids=[bid.serializable_dict() for bid in market.bids.values()],
                offers=[
                    offer.serializable_dict()
                    for offer in market.offers.values()
                ],
                trade_rate=1,
                selected_energy=1,
                market_id=market.id).serializable_dict()
        ]
        market.match_recommendations(recommendations)
        assert len(market.trades) == 2
 def test_matching_list_gets_updated_with_residual_offers(self):
     matches = [
         BidOfferMatch(offers=[
             Offer("offer_id", pendulum.now(), 1, 1,
                   "S").serializable_dict()
         ],
                       selected_energy=1,
                       bids=[
                           Bid("bid_id", pendulum.now(), 1, 1,
                               "B").serializable_dict()
                       ],
                       trade_rate=1,
                       market_id="").serializable_dict(),
         BidOfferMatch(offers=[
             Offer("offer_id2", pendulum.now(), 2, 2,
                   "S").serializable_dict()
         ],
                       selected_energy=2,
                       bids=[
                           Bid("bid_id2", pendulum.now(), 2, 2,
                               "B").serializable_dict()
                       ],
                       trade_rate=1,
                       market_id="").serializable_dict()
     ]
     offer_trade = Trade("trade",
                         1,
                         Offer("offer_id", pendulum.now(), 1, 1, "S"),
                         "S",
                         "B",
                         residual=Offer("residual_offer", pendulum.now(),
                                        0.5, 0.5, "S"))
     bid_trade = Trade("bid_trade",
                       1,
                       Bid("bid_id2", pendulum.now(), 1, 1, "S"),
                       "S",
                       "B",
                       residual=Bid("residual_bid_2", pendulum.now(), 1, 1,
                                    "S"))
     matches = TwoSidedMarket._replace_offers_bids_with_residual_in_recommendations_list(
         matches, offer_trade, bid_trade)
     assert len(matches) == 2
     assert matches[0]["offers"][0]["id"] == "residual_offer"
     assert matches[1]["bids"][0]["id"] == "residual_bid_2"
Example #4
0
    def match_recommendations(
            self,
            recommendations: List[BidOfferMatch.serializable_dict]) -> None:
        """Match a list of bid/offer pairs, create trades and residual offers/bids."""

        while recommendations:
            recommended_pair = recommendations.pop(0)
            recommended_pair = BidOfferMatch.from_dict(recommended_pair)
            selected_energy = recommended_pair.selected_energy
            clearing_rate = recommended_pair.trade_rate
            market_offers = [
                self.offers.get(offer["id"])
                for offer in recommended_pair.offers
            ]
            market_bids = [
                self.bids.get(bid["id"]) for bid in recommended_pair.bids
            ]

            if not all(market_offers) and all(market_bids):
                # If not all offers bids exist in the market, skip the current recommendation
                continue

            self.validate_bid_offer_match(market_bids, market_offers,
                                          clearing_rate, selected_energy)

            market_offers = iter(market_offers)
            market_bids = iter(market_bids)
            market_offer = next(market_offers, None)
            market_bid = next(market_bids, None)
            while market_bid and market_offer:
                original_bid_rate = market_bid.original_bid_price / market_bid.energy
                trade_bid_info = TradeBidOfferInfo(
                    original_bid_rate=original_bid_rate,
                    propagated_bid_rate=market_bid.energy_rate,
                    original_offer_rate=market_offer.original_offer_price /
                    market_offer.energy,
                    propagated_offer_rate=market_offer.energy_rate,
                    trade_rate=original_bid_rate)

                bid_trade, offer_trade = self.accept_bid_offer_pair(
                    market_bid, market_offer, clearing_rate, trade_bid_info,
                    min(selected_energy, market_offer.energy,
                        market_bid.energy))
                if offer_trade.residual:
                    market_offer = offer_trade.residual
                else:
                    market_offer = next(market_offers, None)
                if bid_trade.residual:
                    market_bid = bid_trade.residual
                else:
                    market_bid = next(market_bids, None)
                recommendations = (
                    self.
                    _replace_offers_bids_with_residual_in_recommendations_list(
                        recommendations, offer_trade, bid_trade))
    def _get_validated_recommendations(
            self, recommendations: List[Dict]
    ) -> List[BidOfferMatch.serializable_dict]:
        """Return a validated list of BidOfferMatch instances."""
        validated_recommendations = []
        for recommendation in recommendations:
            if not BidOfferMatch.is_valid_dict(recommendation):
                raise MycoValidationException("BidOfferMatch is not valid")
            market = self.markets_mapping.get(recommendation.get("market_id"))
            if market is None:
                # The market doesn't exist
                raise MycoValidationException(
                    f"Market with id {recommendation.get('market_id')} doesn't exist."
                )
            if market.readonly:
                # The market is already finished
                # TODO: we're clearing markets cache after each market cycle so is this check
                # TODO: .. really relevant? (All finished markets should be deleted already)
                raise MycoValidationException(
                    "Cannot match trades in a finished market.")

            # Get the original bid and offer from the market
            market_bids = [
                market.bids.get(bid.get("id"))
                for bid in recommendation.get("bids")
            ]
            market_offers = [
                market.offers.get(offer.get("id"))
                for offer in recommendation.get("offers")
            ]

            if not (all(market_bids) and all(market_offers)):
                # Offers or Bids either don't belong to market or were already matched
                continue
            try:
                market.validate_bid_offer_match(
                    market_bids, market_offers,
                    recommendation.get("trade_rate"),
                    recommendation.get("selected_energy"))
            except InvalidBidOfferPairException:
                continue

            recommendation["bids"] = [
                bid.serializable_dict() for bid in market_bids
            ]
            recommendation["offers"] = [
                offer.serializable_dict() for offer in market_offers
            ]
            validated_recommendations.append(recommendation)
        return validated_recommendations
Example #6
0
def perform_pay_as_bid_match(market_offers_bids_list_mapping):
    """Performs pay as bid matching algorithm.

    There are 2 simplistic approaches to the problem
        1. Match the cheapest offer with the most expensive bid. This will favor the sellers
        2. Match the cheapest offer with the cheapest bid. This will favor the buyers,
           since the most affordable offers will be allocated for the most aggressive buyers.
    Args:
        market_offers_bids_list_mapping: dict {market_uuid: {"offers": [...], "bids": [...]}, }

    Returns: List[BidOfferMatch.serializable_dict()]
    """
    bid_offer_pairs = []
    for market_id, data in market_offers_bids_list_mapping.items():
        bids = data.get("bids")
        offers = data.get("offers")
        # Sorted bids in descending order
        sorted_bids = sort_list_of_dicts_by_attribute(bids, "energy_rate",
                                                      True)
        # Sorted offers in descending order
        sorted_offers = sort_list_of_dicts_by_attribute(
            offers, "energy_rate", True)
        already_selected_bids = set()
        for offer in sorted_offers:
            for bid in sorted_bids:
                if bid.get("id") in already_selected_bids or\
                        offer.get("seller") == bid.get("buyer"):
                    continue
                if (offer.get("energy_rate") -
                        bid.get("energy_rate")) <= FLOATING_POINT_TOLERANCE:
                    already_selected_bids.add(bid.get("id"))
                    selected_energy = min(bid.get("energy"),
                                          offer.get("energy"))
                    bid_offer_pairs.append(
                        BidOfferMatch(market_id=market_id,
                                      bid=bid,
                                      offer=offer,
                                      selected_energy=selected_energy,
                                      trade_rate=bid.get(
                                          "energy_rate")).serializable_dict())
                    break
    return bid_offer_pairs
    def test_match_recommendations(self, market):
        """Test match_recommendations() method of TwoSidedMarket."""
        bid = Bid("bid_id1", pendulum.now(), price=2, energy=1, buyer="Buyer")
        offer = Offer("offer_id1",
                      pendulum.now(),
                      price=2,
                      energy=1,
                      seller="Seller")

        market.bids = {"bid_id1": bid}
        market.offers = {"offer_id1": offer}

        recommendations = [
            BidOfferMatch(bids=[bid.serializable_dict()],
                          offers=[offer.serializable_dict()],
                          trade_rate=2,
                          selected_energy=1,
                          market_id=market.id).serializable_dict()
        ]
        market.match_recommendations(recommendations)
        assert len(market.trades) == 1