def test_event_trade_after_offer_changed_partial_offer(area_test2, bus_test2): original_offer = Offer(id='old_id', price=20, energy=1, seller='FakeArea') accepted_offer = Offer(id='old_id', price=15, energy=0.75, seller='FakeArea') residual_offer = Offer(id='res_id', price=5, energy=0.25, seller='FakeArea') bus_test2.offers.post(original_offer, area_test2.test_market.id) bus_test2.event_offer_split(market_id=area_test2.test_market.id, original_offer=original_offer, accepted_offer=accepted_offer, residual_offer=residual_offer) assert original_offer.id in bus_test2.offers.split assert bus_test2.offers.split[original_offer.id] == accepted_offer bus_test2.event_trade(market_id=area_test2.test_market.id, trade=Trade(id='id', time='time', offer=original_offer, seller='FakeArea', buyer='buyer')) assert residual_offer in bus_test2.offers.posted assert bus_test2.offers.posted[residual_offer] == area_test2.test_market.id assert len(bus_test2.offers.posted) == 1 assert len(bus_test2.offers.split) == 1 assert len(bus_test2.offers.sold) == 1 assert original_offer.id in bus_test2.offers.sold_in_market( area_test2.test_market.id)
def test_create_bid_offer_matchings_can_handle_excessive_offer_energy( self): bid_list = [ Bid('bid_id', 1, 1, 'B', 'S'), Bid('bid_id1', 2, 2, 'B', 'S'), Bid('bid_id2', 3, 3, 'B', 'S'), Bid('bid_id3', 4, 4, 'B', 'S'), Bid('bid_id4', 5, 5, 'B', 'S') ] offer_list = [ Offer('offer_id', 8, 8, 'S'), Offer('offer_id1', 4, 4, 'S'), Offer('offer_id2', 13, 13, 'S'), ] matchings = TwoSidedPayAsClear._create_bid_offer_matchings( 15, offer_list, bid_list) assert len(matchings) == 7 self.validate_matching(matchings[0], 1, 'offer_id', 'bid_id') self.validate_matching(matchings[1], 2, 'offer_id', 'bid_id1') self.validate_matching(matchings[2], 3, 'offer_id', 'bid_id2') self.validate_matching(matchings[3], 2, 'offer_id', 'bid_id3') self.validate_matching(matchings[4], 2, 'offer_id1', 'bid_id3') self.validate_matching(matchings[5], 2, 'offer_id1', 'bid_id4') self.validate_matching(matchings[6], 3, 'offer_id2', 'bid_id4')
def test_event_trade_after_offer_changed_partial_offer(area_test2, commercial_test2): existing_offer = Offer(id='old_id', price=20, energy=1, seller='FakeArea') new_offer = Offer(id='new_id', price=15, energy=0.75, seller='FakeArea') commercial_test2.offers.post(existing_offer, area_test2.test_market) commercial_test2.offers.post(new_offer, area_test2.test_market) commercial_test2.event_offer_changed(market_id=area_test2.test_market.id, existing_offer=existing_offer, new_offer=new_offer) assert existing_offer.id in commercial_test2.offers.changed assert commercial_test2.offers.changed[existing_offer.id] == new_offer commercial_test2.event_trade(market_id=area_test2.test_market.id, trade=Trade(id='id', time='time', offer=existing_offer, seller='FakeArea', buyer='buyer')) assert len(commercial_test2.offers.posted) == 2 assert new_offer in commercial_test2.offers.posted assert commercial_test2.offers.posted[new_offer] == area_test2.test_market assert len(commercial_test2.offers.changed) == 0 assert len(commercial_test2.offers.sold) == 1 assert existing_offer.id in commercial_test2.offers.sold[ area_test2.test_market]
def test_matching_list_gets_updated_with_residual_offers(self): matchings = [ BidOfferMatch(offer=Offer('offer_id', pendulum.now(), 1, 1, 'S'), offer_energy=1, bid=Bid('bid_id', pendulum.now(), 1, 1, 'B', 'S'), bid_energy=1), BidOfferMatch(offer=Offer('offer_id2', pendulum.now(), 2, 2, 'S'), offer_energy=2, bid=Bid('bid_id2', pendulum.now(), 2, 2, 'B', 'S'), bid_energy=2) ] 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', 'B'), 'S', 'B', residual=Bid('residual_bid_2', pendulum.now(), 1, 1, 'S', 'B')) matchings = TwoSidedPayAsClear._replace_offers_bids_with_residual_in_matching_list( matchings, 0, offer_trade, bid_trade) assert len(matchings) == 2 assert matchings[0].offer.id == 'residual_offer' assert matchings[1].bid.id == 'residual_bid_2'
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") 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_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_from_JSON_string(payload["kwargs"]["new_offer"], new_offer.time) == new_offer assert isinstance(payload["kwargs"]["existing_offer"], str) assert offer_from_JSON_string(payload["kwargs"]["existing_offer"], existing_offer.time) == \ existing_offer
def test_create_bid_offer_matchings_can_handle_partial_offers(self): bid_list = [ Bid('bid_id', pendulum.now(), 1, 1, 'B', 'S'), Bid('bid_id1', pendulum.now(), 2, 2, 'B', 'S'), Bid('bid_id2', pendulum.now(), 3, 3, 'B', 'S'), Bid('bid_id3', pendulum.now(), 4, 4, 'B', 'S'), Bid('bid_id4', pendulum.now(), 5, 5, 'B', 'S') ] offer_list = [ Offer('offer_id', pendulum.now(), 8, 8, 'S'), Offer('offer_id1', pendulum.now(), 4, 4, 'S'), Offer('offer_id2', pendulum.now(), 3, 3, 'S'), ] matchings = TwoSidedPayAsClear._create_bid_offer_matchings( 15, offer_list, bid_list) self.validate_matching(matchings[0], 1, 'offer_id', 'bid_id') self.validate_matching(matchings[1], 2, 'offer_id', 'bid_id1') self.validate_matching(matchings[2], 3, 'offer_id', 'bid_id2') self.validate_matching(matchings[3], 2, 'offer_id', 'bid_id3') self.validate_matching(matchings[4], 2, 'offer_id1', 'bid_id3') self.validate_matching(matchings[5], 2, 'offer_id1', 'bid_id4') self.validate_matching(matchings[6], 3, 'offer_id2', 'bid_id4')
def offer(self, price, energy, seller, original_offer_price=None, seller_origin=None): offer = Offer('id', price, energy, seller, original_offer_price, seller_origin=seller_origin) self.created_offers.append(offer) offer.id = 'id' return offer
def accept_offer(self, offer_or_id, buyer, *, energy=None, time=None, already_tracked=False, trade_rate: float = None, trade_bid_info=None, buyer_origin=None): if isinstance(offer_or_id, Offer): offer_or_id = offer_or_id.id offer = self.offers.pop(offer_or_id, None) if offer is None: assert False self.calls_energy.append(energy) self.calls_offers.append(offer) if energy < offer.energy: residual_energy = offer.energy - energy residual = Offer('res', offer.price, residual_energy, offer.seller) traded = Offer(offer.id, offer.price, energy, offer.seller) return Trade('trade_id', time, traded, traded.seller, buyer, residual) else: return Trade('trade_id', time, offer, offer.seller, buyer)
def test_create_bid_offer_matchings_respects_offer_bid_order( self, en1, en2, en3, en4, en5, clearing_energy): bid_list = [ Bid('bid_id', pendulum.now(), 1, en1, 'B', 'S'), Bid('bid_id1', pendulum.now(), 2, en2, 'B', 'S'), Bid('bid_id2', pendulum.now(), 3, en3, 'B', 'S'), Bid('bid_id3', pendulum.now(), 4, en4, 'B', 'S'), Bid('bid_id4', pendulum.now(), 5, en5, 'B', 'S') ] offer_list = [ Offer('offer_id', pendulum.now(), 1, en1, 'S'), Offer('offer_id1', pendulum.now(), 2, en2, 'S'), Offer('offer_id2', pendulum.now(), 3, en3, 'S'), Offer('offer_id3', pendulum.now(), 4, en4, 'S'), Offer('offer_id4', pendulum.now(), 5, en5, 'S') ] matchings = TwoSidedPayAsClear._create_bid_offer_matchings( clearing_energy, offer_list, bid_list) assert len(matchings) == 5 self.validate_matching(matchings[0], en1, 'offer_id', 'bid_id') self.validate_matching(matchings[1], en2, 'offer_id1', 'bid_id1') self.validate_matching(matchings[2], en3, 'offer_id2', 'bid_id2') self.validate_matching(matchings[3], en4, 'offer_id3', 'bid_id3') self.validate_matching(matchings[4], en5, 'offer_id4', 'bid_id4')
def test_matching_list_affects_only_matches_after_start_index(self): matchings = [ BidOfferMatch(offer=Offer('offer_id', 1, 1, 'S'), offer_energy=1, bid=Bid('bid_id', 1, 1, 'B', 'S'), bid_energy=1), BidOfferMatch(offer=Offer('offer_id2', 2, 2, 'S'), offer_energy=2, bid=Bid('bid_id2', 2, 2, 'B', 'S'), bid_energy=2), BidOfferMatch(offer=Offer('offer_id', 1, 1, 'S'), offer_energy=1, bid=Bid('bid_id', 1, 1, 'B', 'S'), bid_energy=1) ] offer_trade = Trade('trade', 1, Offer('offer_id', 1, 1, 'S'), 'S', 'B', residual=Offer('residual_offer', 0.5, 0.5, 'S')) bid_trade = Trade('bid_trade', 1, Bid('bid_id2', 1, 1, 'S', 'B'), 'S', 'B', residual=Bid('residual_bid_2', 1, 1, 'S', 'B')) matchings = TwoSidedPayAsClear._replace_offers_bids_with_residual_in_matching_list( matchings, 1, offer_trade, bid_trade) assert len(matchings) == 3 assert matchings[0].offer.id == 'offer_id' assert matchings[1].bid.id == 'residual_bid_2' assert matchings[2].offer.id == 'residual_offer'
def test_event_trade_after_offer_changed_partial_offer(area_test2, commercial_test2): original_offer = Offer(id="old_id", time=pendulum.now(), price=20, energy=1, seller="FakeArea") accepted_offer = Offer(id="old_id", time=pendulum.now(), price=15, energy=0.75, seller="FakeArea") residual_offer = Offer(id="res_id", time=pendulum.now(), price=5, energy=0.25, seller="FakeArea") commercial_test2.offers.post(original_offer, area_test2.test_market.id) commercial_test2.event_offer_split(market_id=area_test2.test_market.id, original_offer=original_offer, accepted_offer=accepted_offer, residual_offer=residual_offer) assert original_offer.id in commercial_test2.offers.split assert commercial_test2.offers.split[original_offer.id] == accepted_offer commercial_test2.event_trade(market_id=area_test2.test_market.id, trade=Trade(id="id", time="time", offer_bid=original_offer, seller="FakeArea", buyer="buyer") ) assert residual_offer in commercial_test2.offers.posted assert commercial_test2.offers.posted[residual_offer] == area_test2.test_market.id assert len(commercial_test2.offers.posted) == 1 assert len(commercial_test2.offers.split) == 1 assert len(commercial_test2.offers.sold) == 1 assert original_offer in commercial_test2.offers.sold_in_market(area_test2.test_market.id)
def test_energy_origin(storage_strategy_test15, market_test15): storage_strategy_test15.event_activate() assert len(storage_strategy_test15.state.get_used_storage_share) == 1 assert storage_strategy_test15.state.get_used_storage_share[0] == EnergyOrigin( ESSEnergyOrigin.EXTERNAL, 15) storage_strategy_test15.area.current_market.trade = \ Trade('id', 'time', Offer('id', 20, 1.0, 'ChildArea'), 'ChildArea', 'FakeArea') storage_strategy_test15.event_trade(market_id=market_test15.id, trade=storage_strategy_test15.area.current_market.trade) assert len(storage_strategy_test15.state.get_used_storage_share) == 2 assert storage_strategy_test15.state.get_used_storage_share == [EnergyOrigin( ESSEnergyOrigin.EXTERNAL, 15), EnergyOrigin(ESSEnergyOrigin.LOCAL, 1)] storage_strategy_test15.area.current_market.trade = \ Trade('id', 'time', Offer('id', 20, 2.0, 'FakeArea'), 'FakeArea', 'A') storage_strategy_test15.event_trade(market_id=market_test15.id, trade=storage_strategy_test15.area.current_market.trade) assert len(storage_strategy_test15.state.get_used_storage_share) == 2 assert storage_strategy_test15.state.get_used_storage_share == [EnergyOrigin( ESSEnergyOrigin.EXTERNAL, 13), EnergyOrigin(ESSEnergyOrigin.LOCAL, 1)] storage_strategy_test15.area.current_market.trade = \ Trade('id', 'time', Offer('id', 20, 1.0, 'ParentArea'), 'FakeArea', 'FakeArea') storage_strategy_test15.event_trade(market_id=market_test15.id, trade=storage_strategy_test15.area.current_market.trade) assert len(storage_strategy_test15.state.get_used_storage_share) == 3 assert storage_strategy_test15.state.get_used_storage_share == [EnergyOrigin( ESSEnergyOrigin.EXTERNAL, 13), EnergyOrigin(ESSEnergyOrigin.LOCAL, 1), EnergyOrigin(ESSEnergyOrigin.EXTERNAL, 1)]
def cheapest_offers(self): offers = [[Offer('id', 12, 0.4, 'A', self)], [Offer('id', 12, 0.4, 'A', self)], [Offer('id', 20, 1, 'A', self)], [Offer('id', 20, 5.1, 'A', self)], [Offer('id', 20, 5.1, 'A', market=self.current_market)]] return offers[self.count]
def test_publish_event_subscribes_to_response_and_publishes(self): offer = Offer("1", now(), 2, 3, "A") trade = Trade("2", now(), Offer("accepted", now(), 7, 8, "Z"), "B", "C") 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 } self.publisher.publish_event(MarketEvent.OFFER, **kwargs) self.publisher.redis.sub_to_channel.assert_called_once_with( "market/test_id/notify_event/response", self.publisher.response_callback) expected_result = {k: v.to_JSON_string() for k, v in kwargs.items()} self.publisher.redis.publish.assert_called_once() assert self.publisher.redis.publish.call_args_list[0][0][0] == \ "market/test_id/notify_event" publish_call_args = json.loads( self.publisher.redis.publish.call_args_list[0][0][1]) assert publish_call_args["event_type"] == MarketEvent.OFFER.value assert len(DeepDiff(publish_call_args["kwargs"], expected_result)) == 0
def test_offer_calls_market_method_and_publishes_response(self): payload = { "data": json.dumps({ "seller": "mykonos", "energy": 12, "price": 32, "transaction_uuid": "trans_id" }) } offer = Offer("o_id", now(), 32, 12, "o_seller") self.market.offer = MagicMock(return_value=offer) self.subscriber._offer(payload) sleep(0.01) self.subscriber.market.offer.assert_called_once_with(seller="mykonos", energy=12, price=32) self.subscriber.redis_db.publish.assert_called_once_with( "id/OFFER/RESPONSE", json.dumps({ "status": "ready", "offer": offer.to_JSON_string(), "transaction_uuid": "trans_id" }))
def test_accept_offer_calls_market_method_and_publishes_response(self): offer = Offer("o_id", now(), 12, 13, "o_seller") payload = { "data": json.dumps({ "buyer": "mykonos", "energy": 12, "offer_or_id": offer.to_JSON_string(), "transaction_uuid": "trans_id" }) } trade = Trade(id="trade_id", time=now(), offer=offer, seller="trade_seller", buyer="trade_buyer") self.market.accept_offer = MagicMock(return_value=trade) self.subscriber._accept_offer(payload) sleep(0.01) self.subscriber.market.accept_offer.assert_called_once_with( offer_or_id=offer, buyer="mykonos", energy=12) self.subscriber.redis_db.publish.assert_called_once_with( "id/ACCEPT_OFFER/RESPONSE", json.dumps({ "status": "ready", "trade": trade.to_JSON_string(), "transaction_uuid": "trans_id" }))
def sorted_offers(self): offers = [ [Offer('id', 11.8, 0.5, 'A')], [Offer('id2', 20, 0.5, 'A')], [Offer('id3', 20, 1, 'A')], [Offer('id4', 19, 5.1, 'A')] ] return offers[self.count]
def test_assert_if_trade_rate_is_lower_than_offer_rate(pv_test11): market_id = "market_id" pv_test11.offers.sold[market_id] = [Offer("offer_id", pendulum.now(), 30, 1, "FakeArea")] to_cheap_offer = Offer("offer_id", pendulum.now(), 29, 1, "FakeArea") trade = Trade("trade_id", "time", to_cheap_offer, pv_test11, "buyer") with pytest.raises(AssertionError): pv_test11.event_trade(market_id=market_id, trade=trade)
def __init__(self, count): self.id = count self.count = count self.created_offers = [] self.created_balancing_offers = [] self.sorted_offers = [Offer('id', 25., 1., 'other'), Offer('id', 26., 1., 'other')] self.traded_offers = [] self._bids = {TIME: []}
def offers3(offer1): fixture = Offers(FakeStrategy()) fixture.post(offer1, 'market') fixture.post(Offer('id2', pendulum.now(), 1, 1, 'FakeOwner', 'market'), 'market') fixture.post(Offer('id3', pendulum.now(), 1, 1, 'FakeOwner', 'market2'), 'market2') return fixture
def test_on_offer_changed(area_test2, commercial_test2): commercial_test2.event_activate() existing_offer = Offer(id='id', price=20, energy=1, seller='FakeArea') new_offer = Offer(id='new_id', price=15, energy=0.75, seller='FakeArea') commercial_test2.event_offer_changed(market_id=area_test2.test_market.id, existing_offer=existing_offer, new_offer=new_offer) assert existing_offer.id in commercial_test2.offers.changed assert commercial_test2.offers.changed[existing_offer.id] == new_offer
def test_offers_partial_offer(offer1, offers3): accepted_offer = Offer('id', 1, 1.8, offer1.seller, 'market') residual_offer = Offer('new_id', 1, 1.2, offer1.seller, 'market') trade = Trade('trade_id', pendulum.now(tz=TIME_ZONE), accepted_offer, offer1.seller, 'buyer') offers3.on_offer_changed(offer1, residual_offer) offers3.on_trade('market', trade) assert len(offers3.sold_in_market('market')) == 1 assert accepted_offer in offers3.sold_in_market('market')
def accept_offer(self, offer, buyer, *, energy=None, time=None, price_drop=False): self.calls_energy.append(energy) self.calls_offers.append(offer) if energy < offer.energy: residual_energy = offer.energy - energy residual = Offer('res', offer.price, residual_energy, offer.seller, offer.market) traded = Offer(offer.id, offer.price, energy, offer.seller, offer.market) return Trade('trade_id', time, traded, traded.seller, buyer, residual) else: return Trade('trade_id', time, offer, offer.seller, buyer)
def iaa(): lower_market = FakeMarket([Offer('id', 1, 1, 'other')]) higher_market = FakeMarket([Offer('id2', 3, 3, 'owner'), Offer('id3', 0.5, 1, 'owner')]) owner = FakeArea('owner') iaa = OneSidedAgent(owner=owner, higher_market=higher_market, lower_market=lower_market) iaa.event_tick() iaa.owner.current_tick = 14 iaa.event_tick() return iaa
def test_on_offer_split(area_test2, commercial_test2): commercial_test2.event_activate() original_offer = Offer(id='id', price=20, energy=1, seller='FakeArea') accepted_offer = Offer(id='new_id', price=15, energy=0.75, seller='FakeArea') residual_offer = Offer(id='res_id', price=55, energy=0.25, seller='FakeArea') commercial_test2.event_offer_split(market_id=area_test2.test_market.id, original_offer=original_offer, accepted_offer=accepted_offer, residual_offer=residual_offer) assert original_offer.id in commercial_test2.offers.split assert commercial_test2.offers.split[original_offer.id] == accepted_offer
def iaa3(iaa2): fwd_offer = iaa2.higher_market.forwarded_offer fwd_residual = Offer('res_fwd', fwd_offer.price, 1, fwd_offer.seller) iaa2.event_offer_changed(market_id=iaa2.higher_market.id, existing_offer=fwd_offer, new_offer=fwd_residual) iaa2.event_trade(trade=Trade( 'trade_id', pendulum.now(tz=TIME_ZONE), Offer(fwd_offer.id, fwd_offer.price, 1, fwd_offer.seller), 'owner', 'someone_else', fwd_residual), market_id=iaa2.higher_market.id) return iaa2
def test_double_sided_pay_as_clear_market_works_with_floats(pac_market): ConstSettings.IAASettings.PAY_AS_CLEAR_AGGREGATION_ALGORITHM = 1 pac_market.offers = {"offer1": Offer('id1', now(), 1.1, 1, 'other'), "offer2": Offer('id2', now(), 2.2, 1, 'other'), "offer3": Offer('id3', now(), 3.3, 1, 'other')} pac_market.bids = { "bid1": Bid('bid_id1', now(), 3.3, 1, 'B', 'S'), "bid2": Bid('bid_id2', now(), 2.2, 1, 'B', 'S'), "bid3": Bid('bid_id3', now(), 1.1, 1, 'B', 'S')} matched = pac_market._perform_pay_as_clear_matching()[0] assert matched == 2.2
def test_if_storage_doesnt_buy_above_break_even_point(storage_strategy_test2, area_test2): storage_strategy_test2.event_activate() storage_strategy_test2.break_even_buy = 10.0 area_test2.current_market.offers = {'id': Offer('id', 10.1, 1, 'FakeArea', area_test2.current_market)} storage_strategy_test2.event_tick() assert len(storage_strategy_test2.accept_offer.calls) == 0 area_test2.current_market.offers = {'id': Offer('id', 9.9, 1, 'FakeArea', area_test2.current_market)} storage_strategy_test2.event_tick() assert len(storage_strategy_test2.accept_offer.calls) == 0
def __init__(self, count): self.count = count self.id = str(count) self.trade = Trade('id', 'time', Offer('id', 11.8, 0.5, 'FakeArea'), 'FakeArea', 'buyer' ) self.created_offers = [] self.offers = {'id': Offer('id', 11.8, 0.5, 'FakeArea'), 'id2': Offer('id2', 20, 0.5, 'A'), 'id3': Offer('id3', 20, 1, 'A'), 'id4': Offer('id4', 19, 5.1, 'A')} self.bids = {} self.created_balancing_offers = []
def iaa_grid_fee(): lower_market = FakeMarket([Offer('id', 1, 1, 'other')], transfer_fee_ratio=0.1, transfer_fee_const=2) higher_market = FakeMarket([Offer('id2', 3, 3, 'owner'), Offer('id3', 0.5, 1, 'owner')], transfer_fee_ratio=0.1, transfer_fee_const=2) owner = FakeArea('owner') iaa = OneSidedAgent(owner=owner, higher_market=higher_market, lower_market=lower_market) iaa.event_tick() iaa.owner.current_tick = 14 iaa.event_tick() return iaa