def test_market_accept_bid_yields_partial_bid_trade( self, market=TwoSidedMarket(bc=MagicMock(), time_slot=pendulum.now())): bid = market.bid(2.0, 4, "buyer", "buyer") trade_offer_info = TradeBidOfferInfo(2, 2, 1, 1, 2) trade = market.accept_bid(bid, energy=1, seller="seller", trade_offer_info=trade_offer_info) assert trade.offer_bid.id == bid.id and trade.offer_bid.energy == 1
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 propagate_original_bid_info_on_offer_trade(self, trade_original_info): if trade_original_info is None: return None bid_rate = trade_original_info.propagated_bid_rate - self.grid_fee_rate trade_bid_info = TradeBidOfferInfo( original_bid_rate=trade_original_info.original_bid_rate, propagated_bid_rate=bid_rate, original_offer_rate=None, propagated_offer_rate=None, trade_rate=trade_original_info.trade_rate) return trade_bid_info
def propagate_original_offer_info_on_bid_trade(self, trade_original_info, ignore_fees=False): grid_fee_rate = self.grid_fee_rate if not ignore_fees else 0.0 offer_rate = trade_original_info.propagated_offer_rate + grid_fee_rate trade_offer_info = TradeBidOfferInfo( original_bid_rate=None, propagated_bid_rate=None, original_offer_rate=trade_original_info.original_offer_rate, propagated_offer_rate=offer_rate, trade_rate=trade_original_info.trade_rate) return trade_offer_info
def update_forwarded_offer_trade_original_info(self, trade_original_info, market_offer): if not trade_original_info: return None trade_bid_info = TradeBidOfferInfo( original_bid_rate=trade_original_info.original_bid_rate, propagated_bid_rate=trade_original_info.propagated_bid_rate, original_offer_rate=market_offer.original_offer_price / market_offer.energy, propagated_offer_rate=market_offer.energy_rate, trade_rate=trade_original_info.trade_rate) return trade_bid_info
def test_market_trade_partial_bid_invalid(self, energy, market=TwoSidedMarket( bc=MagicMock(), time_slot=pendulum.now())): bid = market.bid(20, 20, "A", "A") trade_offer_info = TradeBidOfferInfo(1, 1, 1, 1, 1) with pytest.raises(InvalidTrade): market.accept_bid(bid, energy=energy, seller="A", trade_offer_info=trade_offer_info)
def test_market_trade_bid_not_found(self, market=TwoSidedMarket( bc=MagicMock(), time_slot=pendulum.now())): bid = market.bid(20, 10, "A", "A") trade_offer_info = TradeBidOfferInfo(2, 2, 1, 1, 2) assert market.accept_bid(bid, 10, "B", trade_offer_info=trade_offer_info) with pytest.raises(BidNotFoundException): market.accept_bid(bid, 10, "B", trade_offer_info=trade_offer_info)
def test_market_accept_bid_always_updates_trade_stats( self, called, market_method, market=TwoSidedMarket(bc=MagicMock(), time_slot=pendulum.now())): setattr(market, market_method, called) bid = market.bid(20, 20, "A", "A") trade_offer_info = TradeBidOfferInfo(1, 1, 1, 1, 1) trade = market.accept_bid(bid, energy=5, seller="B", trade_offer_info=trade_offer_info) assert trade assert len(getattr(market, market_method).calls) == 1
def test_market_bid_trade(self, market=TwoSidedMarket(bc=MagicMock(), time_slot=pendulum.now())): bid = market.bid(20, 10, "A", "A", original_bid_price=20) trade_offer_info = TradeBidOfferInfo(2, 2, 0.5, 0.5, 2) trade = market.accept_bid(bid, energy=10, seller="B", trade_offer_info=trade_offer_info) assert trade assert trade.id == market.trades[0].id assert trade.id assert trade.offer_bid.price == bid.price assert trade.offer_bid.energy == bid.energy assert trade.seller == "B" assert trade.buyer == "A" assert not trade.residual
def test_market_accept_bid_emits_bid_split_on_partial_bid( self, called, market=TwoSidedMarket(bc=MagicMock(), time_slot=pendulum.now())): market.add_listener(called) bid = market.bid(20, 20, "A", "A") trade_offer_info = TradeBidOfferInfo(1, 1, 1, 1, 1) trade = market.accept_bid(bid, energy=1, trade_offer_info=trade_offer_info) assert all([ ev != repr(MarketEvent.BID_DELETED) for c in called.calls for ev in c[0] ]) assert len(called.calls) == 2 assert called.calls[0][0] == (repr(MarketEvent.BID_SPLIT), ) assert called.calls[1][0] == (repr(MarketEvent.BID_TRADED), ) assert called.calls[1][1] == { "market_id": repr(market.id), "bid_trade": repr(trade), }
def test_market_trade_bid_partial(self, market=TwoSidedMarket( bc=MagicMock(), time_slot=pendulum.now())): bid = market.bid(20, 20, "A", "A", original_bid_price=20) trade_offer_info = TradeBidOfferInfo(1, 1, 1, 1, 1) trade = market.accept_bid(bid, energy=5, seller="B", trade_offer_info=trade_offer_info) assert trade assert trade.id == market.trades[0].id assert trade.id assert trade.offer_bid is not bid assert trade.offer_bid.energy == 5 assert trade.offer_bid.price == 5 assert trade.seller == "B" assert trade.buyer == "A" assert trade.residual assert len(market.bids) == 1 assert trade.residual.id in market.bids assert market.bids[trade.residual.id].energy == 15 assert isclose(market.bids[trade.residual.id].price, 15) assert market.bids[trade.residual.id].buyer == "A"
def test_publish_event_converts_python_objects_to_json(self): offer = Offer("1", now(), 2, 3, "A") trade = Trade("2", now(), Offer("accepted", now(), 7, 8, "Z"), "B", "C", None, None, TradeBidOfferInfo(None, None, None, None, None)) new_offer = Offer("3", now(), 4, 5, "D") existing_offer = Offer("4", now(), 5, 6, "E") kwargs = { "offer": offer, "trade": trade, "new_offer": new_offer, "existing_offer": existing_offer } for dispatcher in [ self.area.dispatcher.market_event_dispatcher, self.device1.dispatcher.market_event_dispatcher, self.device2.dispatcher.market_event_dispatcher ]: dispatcher.publish_event(dispatcher.area.uuid, MarketEvent.OFFER, **kwargs) assert dispatcher.redis.publish.call_count == 1 payload = json.loads( dispatcher.redis.publish.call_args_list[0][0][1]) assert isinstance(payload["kwargs"]["offer"], str) assert offer_or_bid_from_json_string(payload["kwargs"]["offer"], offer.time) == offer assert isinstance(payload["kwargs"]["trade"], str) assert trade_from_json_string(payload["kwargs"]["trade"], trade.time) == trade assert isinstance(payload["kwargs"]["new_offer"], str) assert offer_or_bid_from_json_string( payload["kwargs"]["new_offer"], new_offer.time) == new_offer assert isinstance(payload["kwargs"]["existing_offer"], str) assert offer_or_bid_from_json_string( payload["kwargs"]["existing_offer"], existing_offer.time) == existing_offer