def iaa_bid(): ConstSettings.IAASettings.MARKET_TYPE = 2 lower_market = FakeMarket([], [Bid('id', 1, 1, 'this', 'other', 1, buyer_origin='id')]) higher_market = FakeMarket([], [Bid('id2', 1, 1, 'child', 'owner', 1, buyer_origin='id2'), Bid('id3', 0.5, 1, 'child', 'owner', 1, buyer_origin='id3')]) owner = FakeArea('owner') iaa = TwoSidedPayAsBidAgent(owner=owner, higher_market=higher_market, lower_market=lower_market) iaa.event_tick() iaa.owner.current_tick = 14 iaa.event_tick() yield iaa
def test_assert_if_trade_rate_is_higher_than_bid_rate(storage_test11): market_id = "2" storage_test11._bids[market_id] = \ [Bid("bid_id", now(), 30, 1, buyer="FakeArea", seller="producer")] expensive_bid = Bid("bid_id", now(), 31, 1, buyer="FakeArea", seller="producer") trade = Trade("trade_id", "time", expensive_bid, storage_test11, "buyer") with pytest.raises(AssertionError): storage_test11.event_trade(market_id=market_id, trade=trade)
def test_iaa_forwards_partial_bid_from_source_market(iaa_double_sided): iaa_double_sided._get_market_from_market_id = lambda x: iaa_double_sided.lower_market original_bid = iaa_double_sided.lower_market._bids[0] residual_energy = 0.1 accepted_bid = Bid(original_bid.id, original_bid.time, original_bid.price, original_bid.energy - residual_energy, original_bid.buyer, original_bid.seller, original_bid.price) residual_bid = Bid('residual_bid', original_bid.time, original_bid.price, residual_energy, original_bid.buyer, original_bid.seller, original_bid.price) iaa_double_sided.usable_bid = lambda s: True iaa_double_sided.event_bid_split(market_id=iaa_double_sided.lower_market, original_bid=original_bid, accepted_bid=accepted_bid, residual_bid=residual_bid) assert isclose(iaa_double_sided.higher_market.forwarded_bid.energy, residual_energy)
def iaa_bid(): ConstSettings.IAASettings.MARKET_TYPE = 2 lower_market = FakeMarket([], [Bid('id', 1, 1, 'this', 'other')]) higher_market = FakeMarket([], [Bid('id2', 3, 3, 'child', 'owner'), Bid('id3', 0.5, 1, 'child', 'owner')]) owner = FakeArea('owner') iaa = TwoSidedPayAsBidAgent(owner=owner, higher_market=higher_market, lower_market=lower_market, transfer_fee_pct=5) iaa.event_tick(area=iaa.owner) iaa.owner.current_tick = 14 iaa.event_tick(area=iaa.owner) yield iaa ConstSettings.IAASettings.MARKET_TYPE = 1
def test_accept_bid_calls_market_method_and_publishes_response(self): bid = Bid("b_id", now(), 12, 13, "b_buyer", "b_seller") payload = { "data": json.dumps({ "seller": "mykonos", "energy": 12, "bid": bid.to_JSON_string(), "transaction_uuid": "trans_id" }) } trade = Trade(id="trade_id", time=now(), offer=bid, seller="trade_seller", buyer="trade_buyer") self.market.accept_bid = MagicMock(return_value=trade) self.subscriber._accept_bid(payload) self.subscriber.market.accept_bid.assert_called_once() self.subscriber.redis_db.publish.assert_called_once_with( "id/ACCEPT_BID/RESPONSE", json.dumps({ "status": "ready", "trade": trade.to_JSON_string(), "transaction_uuid": "trans_id" }))
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', 1.1, 1, 'other'), "offer2": Offer('id2', 2.2, 1, 'other'), "offer3": Offer('id3', 3.3, 1, 'other') } pac_market.bids = { "bid1": Bid('bid_id1', 3.3, 1, 'B', 'S'), "bid2": Bid('bid_id2', 2.2, 1, 'B', 'S'), "bid3": Bid('bid_id3', 1.1, 1, 'B', 'S') } matched = pac_market._perform_pay_as_clear_matching()[0] assert matched == 2.2
def bid(self, price: float, energy: float, buyer: str, bid_id: str = None, original_bid_price=None, buyer_origin=None, adapt_price_with_fees=True, buyer_origin_id=None, buyer_id=None): self.bid_call_count += 1 if original_bid_price is None: original_bid_price = price if bid_id is None: bid_id = "uuid" if adapt_price_with_fees: price = self._update_new_bid_price_with_fee( price, original_bid_price) bid = Bid(bid_id, pendulum.now(), price, energy, buyer, original_bid_price=original_bid_price, buyer_origin=buyer_origin, buyer_origin_id=buyer_origin_id, buyer_id=buyer_id) self._bids.append(bid) self.forwarded_bid = bid return bid
def test_iaa_forwards_offers_according_to_percentage(iaa_fee): ConstSettings.IAASettings.MARKET_TYPE = 2 lower_market = FakeMarket([], [Bid('id', 1, 1, 'this', 'other', 1)], transfer_fee_ratio=iaa_fee) higher_market = FakeMarket([], [Bid('id2', 3, 3, 'child', 'owner', 3)], transfer_fee_ratio=iaa_fee) iaa = TwoSidedPayAsBidAgent(owner=FakeArea('owner'), higher_market=higher_market, lower_market=lower_market) iaa.event_tick() iaa.owner.current_tick = 14 iaa.event_tick() assert iaa.higher_market.bid_count == 1 assert iaa.higher_market.forwarded_bid.price == \ list(iaa.lower_market.bids.values())[-1].price * (1 - iaa_fee)
def bid(self, price: float, energy: float, buyer: str, seller: str, buyer_origin, bid_id: str = None, original_bid_price=None, adapt_price_with_fees=True) -> Bid: if energy <= 0: raise InvalidBid() if original_bid_price is None: original_bid_price = price if adapt_price_with_fees: price = self._update_new_bid_price_with_fee( price, original_bid_price) bid = Bid( str(uuid.uuid4()) if bid_id is None else bid_id, self.now, price, energy, buyer, seller, original_bid_price, buyer_origin) self.bids[bid.id] = bid self.bid_history.append(bid) log.debug(f"[BID][NEW][{self.time_slot_str}] {bid}") return bid
def test_iaa_forwards_offers_according_to_constantfee(iaa_fee_const): ConstSettings.IAASettings.MARKET_TYPE = 2 lower_market = FakeMarket([], [Bid('id', 15, 1, 'this', 'other', 15)], transfer_fee_const=iaa_fee_const) higher_market = FakeMarket([], [Bid('id2', 35, 3, 'child', 'owner', 35)], transfer_fee_const=iaa_fee_const) iaa = TwoSidedPayAsBidAgent(owner=FakeArea('owner'), higher_market=higher_market, lower_market=lower_market) iaa.event_tick() iaa.owner.current_tick = 14 iaa.event_tick() assert iaa.higher_market.bid_count == 1 bid = list(iaa.lower_market.bids.values())[-1] assert iaa.higher_market.forwarded_bid.price == bid.price - iaa_fee_const * bid.energy
def test_assert_if_trade_rate_is_higher_than_bid_rate( load_hours_strategy_test3): market_id = 0 load_hours_strategy_test3._bids[market_id] = \ [Bid("bid_id", now(), 30, 1, buyer="FakeArea", seller="producer")] expensive_bid = Bid("bid_id", now(), 31, 1, buyer="FakeArea", seller="producer") trade = Trade("trade_id", "time", expensive_bid, load_hours_strategy_test3, "buyer") with pytest.raises(AssertionError): load_hours_strategy_test3.event_trade(market_id=market_id, trade=trade)
def test_bid_deleted_removes_bid_from_posted(base): ConstSettings.IAASettings.MARKET_TYPE = 2 test_bid = Bid("123", pendulum.now(), 12, 23, base.owner.name, 'B') market = FakeMarket(raises=False, id=21) base.area._market = market base._bids[market.id] = [test_bid] base.event_bid_deleted(market_id=21, bid=test_bid) assert base.get_posted_bids(market) == []
def test_double_sided_market_performs_pay_as_clear_matching( pac_market, offer, bid, mcp_rate, mcp_energy, algorithm): ConstSettings.IAASettings.PAY_AS_CLEAR_AGGREGATION_ALGORITHM = algorithm pac_market.offers = { "offer1": Offer('id1', offer[0], 1, 'other'), "offer2": Offer('id2', offer[1], 1, 'other'), "offer3": Offer('id3', offer[2], 1, 'other'), "offer4": Offer('id4', offer[3], 1, 'other'), "offer5": Offer('id5', offer[4], 1, 'other'), "offer6": Offer('id6', offer[5], 1, 'other'), "offer7": Offer('id7', offer[6], 1, 'other') } pac_market.bids = { "bid1": Bid('bid_id1', bid[0], 1, 'B', 'S'), "bid2": Bid('bid_id2', bid[1], 1, 'B', 'S'), "bid3": Bid('bid_id3', bid[2], 1, 'B', 'S'), "bid4": Bid('bid_id4', bid[3], 1, 'B', 'S'), "bid5": Bid('bid_id5', bid[4], 1, 'B', 'S'), "bid6": Bid('bid_id6', bid[5], 1, 'B', 'S'), "bid7": Bid('bid_id7', bid[6], 1, 'B', 'S') } matched_rate, matched_energy = pac_market._perform_pay_as_clear_matching() assert matched_rate == mcp_rate assert matched_energy == mcp_energy
def accept_bid(self, bid: Bid, energy: float = None, seller: str = None, buyer: str = None, already_tracked: bool = False, price_drop: bool = True): market_bid = self.bids.pop(bid.id, None) if market_bid is None: raise BidNotFound("During accept bid: " + str(bid)) seller = market_bid.seller if seller is None else seller buyer = market_bid.buyer if buyer is None else buyer energy = market_bid.energy if energy is None else energy if energy <= 0: raise InvalidTrade("Energy cannot be zero.") elif energy > bid.energy: raise InvalidTrade( "Traded energy cannot be more than the bid energy.") elif energy is None or energy <= market_bid.energy: residual = False if energy < market_bid.energy: # Partial bidding residual = True # For the residual bid we use the market rate, in order to not affect # rate increase algorithm. energy_rate = market_bid.price / market_bid.energy residual_energy = market_bid.energy - energy residual_price = residual_energy * energy_rate self.bid(residual_price, residual_energy, buyer, seller, bid.id) # For the accepted bid we use the 'clearing' rate from the bid # input argument. final_price = energy * (bid.price / bid.energy) bid = Bid(bid.id, final_price, energy, buyer, seller, self) trade = Trade(str(uuid.uuid4()), self._now, bid, seller, buyer, residual, price_drop=price_drop, already_tracked=already_tracked) if not already_tracked: self._update_stats_after_trade(trade, bid, bid.buyer, already_tracked) log.warning(f"[TRADE][BID][{self.time_slot_str}] {trade}") self._notify_listeners(MarketEvent.BID_TRADED, bid_trade=trade) if not trade.residual: self._notify_listeners(MarketEvent.BID_DELETED, bid=market_bid) return trade else: raise Exception( "Undefined state or conditions. Should never reach this place." )
def test_iaa_event_trade_buys_partial_accepted_bid(iaa_double_sided): iaa_double_sided._get_market_from_market_id = lambda x: iaa_double_sided.higher_market total_bid = iaa_double_sided.higher_market.forwarded_bid accepted_bid_price = (total_bid.price / total_bid.energy) * 1 residual_bid_price = (total_bid.price / total_bid.energy) * 0.1 accepted_bid = Bid(total_bid.id, accepted_bid_price, 1, total_bid.buyer, total_bid.seller) residual_bid = Bid('residual_bid', residual_bid_price, 0.1, total_bid.buyer, total_bid.seller) iaa_double_sided.event_bid_changed( market_id=iaa_double_sided.higher_market, existing_bid=total_bid, new_bid=residual_bid) iaa_double_sided.event_bid_traded( bid_trade=Trade('trade_id', pendulum.now(tz=TIME_ZONE), accepted_bid, 'owner', 'someone_else', 'residual_offer'), market_id=iaa_double_sided.higher_market.id) assert iaa_double_sided.lower_market.calls_energy_bids[0] == 1
def test_iaa_event_trade_buys_partial_accepted_bid(iaa_double_sided): iaa_double_sided._get_market_from_market_id = lambda x: iaa_double_sided.higher_market original_bid = iaa_double_sided.higher_market.forwarded_bid accepted_bid_price = (original_bid.price / original_bid.energy) * 1 residual_bid_price = (original_bid.price / original_bid.energy) * 0.1 accepted_bid = Bid(original_bid.id, original_bid.time, accepted_bid_price, 1, original_bid.buyer) residual_bid = Bid('residual_bid', original_bid.time, residual_bid_price, 0.1, original_bid.buyer) iaa_double_sided.event_bid_split(market_id=iaa_double_sided.higher_market, original_bid=original_bid, accepted_bid=accepted_bid, residual_bid=residual_bid) iaa_double_sided.event_bid_traded( bid_trade=Trade('trade_id', pendulum.now(tz=TIME_ZONE), accepted_bid, 'owner', 'someone_else', 'residual_offer'), market_id=iaa_double_sided.higher_market.id) assert iaa_double_sided.lower_market.calls_energy_bids[0] == 1
def test_bid_changed_adds_bid_to_posted(base): ConstSettings.IAASettings.MARKET_TYPE = 2 test_bid = Bid("123", 12, 23, base.owner.name, 'B') market = FakeMarket(raises=False, id=21) base.area._market = market base._bids[market.id] = [] base.event_bid_changed(market_id=21, existing_bid=test_bid, new_bid=test_bid) assert base.get_posted_bids(market) == [test_bid]
def test_iaa_does_not_forward_bids_if_the_IAA_name_is_the_same_as_the_target_market(iaa_bid): assert iaa_bid.lower_market.bid_count == 2 assert iaa_bid.higher_market.bid_count == 1 engine = next(filter(lambda e: e.name == 'Low -> High', iaa_bid.engines)) engine.owner.name = "TARGET MARKET" iaa_bid.higher_market.area.name = "TARGET MARKET" bid = Bid('id', 1, 1, 'this', 'other') engine._forward_bid(bid) assert iaa_bid.lower_market.bid_count == 2 assert iaa_bid.higher_market.bid_count == 1
def test_bid_events_fail_for_one_sided_market(base): ConstSettings.IAASettings.MARKET_TYPE = 1 test_bid = Bid("123", 12, 23, 'A', 'B') with pytest.raises(AssertionError): base.event_bid_traded(market_id=123, bid_trade=test_bid) with pytest.raises(AssertionError): base.event_bid_deleted(market_id=123, bid=test_bid) with pytest.raises(AssertionError): base.event_bid_changed(market_id=123, existing_bid=test_bid, new_bid=test_bid)
def test_create_bid_offer_matchings_can_match_with_only_one_offer(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, 800000000, 'S')] matchings = TwoSidedPayAsClear._create_bid_offer_matchings( 15, offer_list, bid_list) assert len(matchings) == 5 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], 4, 'offer_id', 'bid_id3') self.validate_matching(matchings[4], 5, 'offer_id', 'bid_id4')
def test_iaa_forwards_offers_according_to_percentage(iaa_fee): ConstSettings.IAASettings.MARKET_TYPE = 2 lower_market = FakeMarket([], [Bid('id', pendulum.now(), 1, 1, 'this', 1)], transfer_fees=GridFee( grid_fee_percentage=iaa_fee, grid_fee_const=0), name="FakeMarket") higher_market = FakeMarket( [], [Bid('id2', pendulum.now(), 3, 3, 'child', 3)], transfer_fees=GridFee(grid_fee_percentage=iaa_fee, grid_fee_const=0), name="FakeMarket") iaa = TwoSidedAgent(owner=FakeArea('owner'), higher_market=higher_market, lower_market=lower_market) iaa.event_tick() iaa.owner.current_tick = 14 iaa.event_tick() assert iaa.higher_market.bid_call_count == 1 assert iaa.higher_market.forwarded_bid.price == \ list(iaa.lower_market.bids.values())[-1].price * (1 - iaa_fee)
def test_iaa_event_trade_buys_partial_accepted_bid(iaa_double_sided): total_bid = iaa_double_sided.higher_market.forwarded_bid accepted_bid = Bid(total_bid.id, total_bid.price, 1, total_bid.buyer, total_bid.seller) iaa_double_sided.event_bid_traded( bid_trade=Trade('trade_id', pendulum.now(tz=TIME_ZONE), accepted_bid, 'owner', 'someone_else', 'residual_offer'), market_id=iaa_double_sided.higher_market.id) assert iaa_double_sided.lower_market.calls_energy_bids[0] == 1
def iaa_double_sided_2(): from d3a.models.const import ConstSettings ConstSettings.IAASettings.MARKET_TYPE = 2 lower_market = FakeMarket(sorted_offers=[Offer('id', 2, 2, 'other')], bids=[Bid('bid_id', 10, 10, 'B', 'S')]) higher_market = FakeMarket([], []) owner = FakeArea('owner') iaa = TwoSidedPayAsBidAgent(owner=owner, lower_market=lower_market, higher_market=higher_market) iaa.event_tick(area=iaa.owner) yield iaa ConstSettings.IAASettings.MARKET_TYPE = 1
def accept_bid(self, bid: Bid, energy: float = None, seller: str = None, buyer: str = None, already_tracked: bool = False, trade_rate: float = None, trade_offer_info=None, time=None, seller_origin=None): self.calls_energy_bids.append(energy) self.calls_bids.append(bid) self.calls_bids_price.append(bid.price) if trade_rate is None: trade_rate = bid.price / bid.energy else: assert trade_rate <= (bid.price / bid.energy) market_bid = [b for b in self.bids.values() if b.id == bid.id][0] if energy < market_bid.energy: residual_energy = bid.energy - energy residual = Bid('res', bid.time, bid.price, residual_energy, bid.buyer, seller) traded = Bid(bid.id, bid.time, (trade_rate * energy), energy, bid.buyer, seller) return Trade('trade_id', time, traded, traded.seller, bid.buyer, residual) else: traded = Bid(bid.id, bid.time, (trade_rate * energy), energy, bid.buyer, seller) return Trade('trade_id', time, traded, traded.seller, bid.buyer)
def test_bid_traded_moves_bid_from_posted_to_traded(base): ConstSettings.IAASettings.MARKET_TYPE = 2 test_bid = Bid("123", pendulum.now(), 12, 23, base.owner.name, 'B') trade = MagicMock() trade.buyer = base.owner.name trade.offer_bid = test_bid market = FakeMarket(raises=False, id=21) base.area._market = market base._bids[market.id] = [test_bid] base.event_bid_traded(market_id=21, bid_trade=trade) assert base.get_posted_bids(market) == [] assert base.get_traded_bids_from_market(market) == [test_bid]
def test_bid_events_fail_for_one_sided_market(base): ConstSettings.IAASettings.MARKET_TYPE = 1 test_bid = Bid("123", pendulum.now(), 12, 23, 'A', 'B') with pytest.raises(AssertionError): base.event_bid_traded(market_id=123, bid_trade=test_bid) with pytest.raises(AssertionError): base.event_bid_deleted(market_id=123, bid=test_bid) with pytest.raises(AssertionError): base.event_bid_split(market_id=123, original_bid=test_bid, accepted_bid=test_bid, residual_bid=test_bid)
def iaa_double_sided(): from d3a_interface.constants_limits import ConstSettings ConstSettings.IAASettings.MARKET_TYPE = 2 lower_market = FakeMarket(sorted_offers=[Offer('id', 2, 2, 'other', 2)], bids=[Bid('bid_id', 10, 10, 'B', 'S', 10)], transfer_fee_ratio=0.01) higher_market = FakeMarket([], [], transfer_fee_ratio=0.01) owner = FakeArea('owner') iaa = TwoSidedPayAsBidAgent(owner=owner, lower_market=lower_market, higher_market=higher_market) iaa.event_tick() iaa.owner.current_tick += 2 iaa.event_tick() yield iaa
def test_iaa_event_bid_split_and_trade_correctly_populate_forwarded_bid_entries(iaa_bid, called, partial): iaa_bid.lower_market.delete_bid = called low_to_high_engine = iaa_bid.engines[0] iaa_bid._get_market_from_market_id = lambda x: low_to_high_engine.markets.target source_bid = list(low_to_high_engine.markets.source.bids.values())[0] target_bid = list(low_to_high_engine.markets.target.bids.values())[0] bidinfo = BidInfo(source_bid=source_bid, target_bid=target_bid) low_to_high_engine.forwarded_bids[source_bid.id] = bidinfo low_to_high_engine.forwarded_bids[target_bid.id] = bidinfo if partial: residual_energy = 0.2 residual_id = "resid" original_bid = Bid(*low_to_high_engine.markets.target._bids[0]) accepted_bid = original_bid._replace( price=(original_bid.energy-residual_energy) * (original_bid.price/original_bid.energy), energy=original_bid.energy-residual_energy) residual_bid = original_bid._replace( id=residual_id, price=residual_energy * (original_bid.price / original_bid.energy), energy=residual_energy) low_to_high_engine.event_bid_split(market_id=low_to_high_engine.markets.target, original_bid=original_bid, accepted_bid=accepted_bid, residual_bid=residual_bid) assert set(low_to_high_engine.forwarded_bids.keys()) == \ {original_bid.id, accepted_bid.id, residual_bid.id, "uuid", "id3", "id2"} else: original_bid = low_to_high_engine.markets.target._bids[0] accepted_bid = deepcopy(original_bid) residual_bid = None low_to_high_engine.event_bid_traded( bid_trade=Trade('trade_id', pendulum.now(tz=TIME_ZONE), accepted_bid, seller='someone_else', buyer='owner', residual=residual_bid)) if partial: # "id" gets traded in the target market, "id2" gets split in the source market, too assert set(low_to_high_engine.forwarded_bids.keys()) == {residual_bid.id, "uuid", "id3"} else: # "id" and "id2" get traded in both target and source, # left over is id3 and its forwarded instance uuid assert set(low_to_high_engine.forwarded_bids.keys()) == {"uuid", "id3"}
def bid(self, price, energy, buyer, seller, original_bid_price=None, buyer_origin=None): bid = Bid("bid_id", price, energy, buyer, seller, buyer_origin=buyer_origin) return bid
def bid(self, price, energy, buyer, seller, original_bid_price=None, buyer_origin=None): return Bid(123, price, energy, buyer, seller, original_bid_price, buyer_origin=buyer_origin)