From 5f1b79218a6bb3d97ddf6a74fed5f7112d75641d Mon Sep 17 00:00:00 2001 From: Florian Maurer Date: Thu, 28 Sep 2023 09:10:25 +0200 Subject: [PATCH] proper fixing for market clearing --- assume/markets/clearing_algorithms/simple.py | 55 ++++++++------------ tests/test_simple_market_mechanisms.py | 29 +++++++++-- tests/utils.py | 6 ++- 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/assume/markets/clearing_algorithms/simple.py b/assume/markets/clearing_algorithms/simple.py index ce87ee10..0d134ae3 100644 --- a/assume/markets/clearing_algorithms/simple.py +++ b/assume/markets/clearing_algorithms/simple.py @@ -60,7 +60,8 @@ def clear( meta = [] orderbook.sort(key=market_getter) for product, product_orders in groupby(orderbook, market_getter): - accepted_product_orders: Orderbook = [] + accepted_demand_orders: Orderbook = [] + accepted_supply_orders: Orderbook = [] product_orders = list(product_orders) if product not in market_products: rejected_orders.extend(product_orders) @@ -89,15 +90,18 @@ def clear( # now add the next demand order dem_vol += -demand_order["volume"] demand_order["accepted_volume"] = demand_order["volume"] - to_commit: Orderbook = [] - # and add supply until the demand order is matched while supply_orders and gen_vol < dem_vol: supply_order = supply_orders.pop(0) if supply_order["price"] <= demand_order["price"]: + added = supply_order["volume"] - supply_order.get( + "accepted_volume", 0 + ) + should_insert = not supply_order.get("accepted_volume") supply_order["accepted_volume"] = supply_order["volume"] - to_commit.append(supply_order) - gen_vol += supply_order["volume"] + if should_insert: + accepted_supply_orders.append(supply_order) + gen_vol += added else: rejected_orders.append(supply_order) # now we know which orders we need @@ -107,17 +111,11 @@ def clear( if diff < 0: # gen < dem - # generation is not enough - split last demand bid - split_demand_order = demand_order.copy() - split_demand_order["accepted_volume"] = diff + # generation is not enough - accept partially demand_order["accepted_volume"] = demand_order["volume"] - diff - rejected_orders.append(split_demand_order) - elif diff > 0: - # generation left over - split last generation bid - supply_order = to_commit[-1] - split_supply_order = supply_order.copy() - split_supply_order["volume"] = diff + # generation left over - accept generation bid partially + supply_order = accepted_supply_orders[-1] supply_order["accepted_volume"] = supply_order["volume"] - diff # changed supply_order is still part of to_commit and will be added @@ -125,21 +123,17 @@ def clear( gen_vol -= diff # add left over to supply_orders again - supply_orders.insert(0, split_supply_order) - + supply_orders.insert(0, supply_order) + demand_order["accepted_volume"] = demand_order["volume"] else: demand_order["accepted_volume"] = demand_order["volume"] - accepted_product_orders.append(demand_order) - accepted_product_orders.extend(to_commit) + accepted_demand_orders.append(demand_order) for order in supply_orders: rejected_orders.append(order) # set clearing price - merit order - uniform pricing - accepted_supply_orders = [ - x for x in accepted_product_orders if x["accepted_volume"] > 0 - ] if accepted_supply_orders: clear_price = float( max(map(itemgetter("price"), accepted_supply_orders)) @@ -147,11 +141,9 @@ def clear( else: clear_price = 0 + accepted_product_orders = accepted_demand_orders + accepted_supply_orders for order in accepted_product_orders: order["accepted_price"] = clear_price - accepted_demand_orders = [ - x for x in accepted_product_orders if x["accepted_volume"] < 0 - ] accepted_orders.extend(accepted_product_orders) meta.append( @@ -186,7 +178,8 @@ def clear( meta = [] orderbook.sort(key=market_getter) for product, product_orders in groupby(orderbook, market_getter): - accepted_product_orders: Orderbook = [] + accepted_demand_orders: Orderbook = [] + accepted_supply_orders: Orderbook = [] if product not in market_products: rejected_orders.extend(product_orders) # log.debug(f'found unwanted bids for {product} should be {market_products}') @@ -246,27 +239,23 @@ def clear( gen_vol -= diff supply_orders.insert(0, split_supply_order) + demand_order["accepted_volume"] = demand_order["volume"] else: # diff == 0 perfect match demand_order["accepted_volume"] = demand_order["volume"] - accepted_orders.append(demand_order) + accepted_demand_orders.append(demand_order) # pay as bid for supply_order in to_commit: supply_order["accepted_price"] = supply_order["price"] demand_order["accepted_price"] = supply_order["price"] - accepted_product_orders.extend(to_commit) + accepted_supply_orders.extend(to_commit) for order in supply_orders: rejected_orders.append(order) - accepted_supply_orders = [ - x for x in accepted_product_orders if x["accepted_volume"] > 0 - ] - accepted_demand_orders = [ - x for x in accepted_product_orders if x["accepted_volume"] < 0 - ] + accepted_product_orders = accepted_demand_orders + accepted_supply_orders accepted_orders.extend(accepted_product_orders) meta.append( diff --git a/tests/test_simple_market_mechanisms.py b/tests/test_simple_market_mechanisms.py index d1f4448c..08167d0b 100644 --- a/tests/test_simple_market_mechanisms.py +++ b/tests/test_simple_market_mechanisms.py @@ -45,9 +45,6 @@ def test_market(): orderbook = extend_orderbook(products, 1000, 100, orderbook) orderbook = extend_orderbook(products, 900, 50, orderbook) - simple_dayahead_auction_config.market_mechanism = clearing_mechanisms[ - simple_dayahead_auction_config.market_mechanism - ] mr = PayAsClearRole(simple_dayahead_auction_config) accepted, rejected, meta = mr.clear(orderbook, products) assert meta[0]["demand_volume"] > 0 @@ -89,3 +86,29 @@ def test_simple_market_mechanism(): # print(meta) # return mr.all_orders, meta + + +def test_market_pay_as_clear(): + next_opening = simple_dayahead_auction_config.opening_hours.after(datetime.now()) + products = get_available_products( + simple_dayahead_auction_config.market_products, next_opening + ) + assert len(products) == 1 + + """ + Create Orderbook with constant order volumes and prices: + - dem1: volume = -1000, price = 3000 + - gen1: volume = 1000, price = 100 + - gen2: volume = 900, price = 50 + """ + orderbook = extend_orderbook(products, -400, 3000) + orderbook = extend_orderbook(products, -100, 3000, orderbook) + orderbook = extend_orderbook(products, 300, 100, orderbook) + orderbook = extend_orderbook(products, 200, 50, orderbook) + + mr = PayAsClearRole(simple_dayahead_auction_config) + accepted, rejected, meta = mr.clear(orderbook, products) + assert meta[0]["demand_volume"] > 0 + assert meta[0]["price"] > 0 + assert len(accepted) == 4 + assert len(rejected) == 0 diff --git a/tests/utils.py b/tests/utils.py index 2b674d8c..7e339282 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -43,7 +43,7 @@ def extend_orderbook( products, volume, price, - orderbook=[], + orderbook=None, bid_type="SB", min_acceptance_ratio=None, ): @@ -52,6 +52,8 @@ def extend_orderbook( with specified values for price and volume and appends the orderbook """ + if not orderbook: + orderbook = [] if volume == 0: return orderbook @@ -94,7 +96,7 @@ def extend_orderbook( "agent_id": agent_id, "bid_id": f"bid_{len(orderbook)+1}", "volume": volume, - "accepted_volume": None, + "accepted_volume": 0, "price": price, "accepted_price": None, "only_hours": None,