/*******************************************************************************
* Copyright 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package emlab.role.market;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.transaction.annotation.Transactional;
import agentspring.role.AbstractRole;
import emlab.domain.market.Bid;
import emlab.domain.market.ClearingPoint;
import emlab.domain.market.DecarbonizationMarket;
import emlab.domain.market.electricity.ElectricitySpotMarket;
import emlab.repository.Reps;
/**
* Calculates {@link ClearingPoint} for any {@link Market}.
*
* @author <a href="mailto:E.J.L.Chappin@tudelft.nl">Emile Chappin</a>
*/
public abstract class AbstractMarketRole<T extends DecarbonizationMarket> extends AbstractRole<T> {
@Autowired
Neo4jTemplate template;
@Autowired
Reps reps;
@Transactional
public ClearingPoint calculateClearingPoint(DecarbonizationMarket market, long time) {
double clearedVolume = 0d;
double clearedPrice = 0d;
double totalSupplyPrice = calculateTotalSupplyPriceForMarketForTime(market, time);
double totalSupply = calculateTotalSupplyForMarketForTime(market, time);
logger.info("total supply {} total price {}", totalSupply, totalSupplyPrice);
double totalDemandForPrice = calculateTotalDemandForMarketForTimeForPrice(market, time, totalSupplyPrice);
logger.info("total demand {} for price {}", totalDemandForPrice, totalSupplyPrice);
// Not enough to meet demand
if (totalDemandForPrice > totalSupply) {
clearedVolume = totalSupply;
if (market.isAuction()) {
clearedPrice = calculateTotalDemandForMarketForTimeForPrice(market, time, 0d);
} else {
clearedPrice = market instanceof ElectricitySpotMarket ? ((ElectricitySpotMarket) market).getValueOfLostLoad()
: totalSupplyPrice;
}
} else { // Supply exceeds demand
double totalOfferAmount = 0d;
double previousPrice = 0d;
for (Bid offer : reps.bidRepository.findOffersForMarketForTime(market, time)) {
double price = offer.getPrice();
double amount = offer.getAmount();
double demand = calculateTotalDemandForMarketForTimeForPrice(market, time, price);
if (demand < totalOfferAmount + amount) {
if (demand == 0) {
if (getCurrentTick() > 0) {
ClearingPoint cp = reps.clearingPointRepository.findClearingPointForMarketAndTime(market, getCurrentTick() - 1);
if (cp != null)
previousPrice = cp.getPrice();
}
clearedPrice = previousPrice;
clearedVolume = totalOfferAmount;
} else if (totalOfferAmount >= demand) {
clearedPrice = previousPrice;
clearedVolume = totalOfferAmount;
} else {
clearedPrice = price;
clearedVolume = demand;
}
break;
}
totalOfferAmount += amount;
previousPrice = price;
}
}
ClearingPoint point = new ClearingPoint().persist();
point.setAbstractMarket(market);
point.setTime(time);
point.setPrice(Math.max(0, clearedPrice));
point.setVolume(clearedVolume);
// set bids to accepted and check for partial acceptance
// DEMAND
double previousPrice = markAcceptedBids(point, false);
// if auction - last accepted demand bid sets the price
if (market.isAuction()) {
point.setPrice(Math.max(0, previousPrice));
}
// SUPPLY
markAcceptedBids(point, true);
return point;
}
private double markAcceptedBids(ClearingPoint point, boolean isSupply) {
long time = point.getTime();
DecarbonizationMarket market = point.getAbstractMarket();
double clearedPrice = point.getPrice();
double clearedVolume = point.getVolume();
double totalBidVolume = 0d;
double previousPrice = Double.NEGATIVE_INFINITY;
double accpetedSamePriceVolume = 0d;
Iterable<Bid> bids = isSupply ? reps.bidRepository.findOffersForMarketForTimeBelowPrice(market, time, clearedPrice) : market
.isAuction() ? reps.bidRepository.findDemandBidsForMarketForTime(market, time) : reps.bidRepository
.findDemandBidsForMarketForTimeAbovePrice(market, time, clearedPrice);
for (Bid bid : bids) {
double amount = bid.getAmount();
totalBidVolume += amount;
accpetedSamePriceVolume = bid.getPrice() == previousPrice ? accpetedSamePriceVolume + amount : amount;
if (totalBidVolume < clearedVolume) {
bid.setStatus(Bid.ACCEPTED);
bid.setAcceptedAmount(bid.getAmount());
} else {
double lastAvailableBidSize = clearedVolume - (totalBidVolume - accpetedSamePriceVolume);
double samePriceVolume = calculateBidsForMarketForTimeForPrice(market, time, bid.getPrice(), isSupply);
double adjustRatio = lastAvailableBidSize / samePriceVolume;
for (Bid partBid : isSupply ? reps.bidRepository.findOffersForMarketForTimeForPrice(market, time, bid.getPrice())
: reps.bidRepository.findDemandBidsForMarketForTimeForPrice(market, time, bid.getPrice())) {
partBid.setStatus(Bid.PARTLY_ACCEPTED);
partBid.setAcceptedAmount(partBid.getAmount() * adjustRatio);
}
break;
}
previousPrice = bid.getPrice();
}
return previousPrice;
}
private double calculateBidsForMarketForTimeForPrice(DecarbonizationMarket market, long time, double price, boolean isSupply) {
try {
return isSupply ? reps.bidRepository.calculateOffersForMarketForTimeForPrice(market, time, price) : reps.bidRepository
.calculateDemandBidsForMarketForTimeForPrice(market, time, price);
} catch (NullPointerException npe) {
}
return 0d;
}
private double calculateTotalDemandForMarketForTimeForPrice(DecarbonizationMarket market, long time, double price) {
try {
return market.isAuction() ? reps.bidRepository.calculateTotalDemandForMarketForTime(market, time) : reps.bidRepository
.calculateTotalDemandForMarketForTimeForPrice(market, time, price);
} catch (NullPointerException npe) {
}
return 0d;
}
private double calculateTotalSupplyPriceForMarketForTime(DecarbonizationMarket market, long time) {
try {
return reps.bidRepository.calculateTotalSupplyPriceForMarketForTime(market, time);
} catch (NullPointerException e) {
}
return 0d;
}
private double calculateTotalSupplyForMarketForTime(DecarbonizationMarket market, long time) {
try {
return reps.bidRepository.calculateTotalSupplyForMarketForTime(market, time);
} catch (NullPointerException e) {
}
return 0d;
}
public abstract Reps getReps();
// -------------------------------------------------------------------------------------------------
// /**
// * Old clearing algorithm. Not deleted yet for compatability reasons. WARNS if it is used.
// *
// * @param market
// * @param supplyBidsIterable
// * @param demandBidsIterable
// * @param time
// * @return
// */
// public ClearingPoint calculateClearingPoint(DecarbonizationMarket market, Iterable<Bid> supplyBidsIterable,
// Iterable<Bid> demandBidsIterable, long time) {
//
// logger.warn("{} still used the old clearing algorithm!", market);
//
// List<Bid> supplyBids = Utils.asList(supplyBidsIterable);
// List<Bid> demandBids = Utils.asList(demandBidsIterable);
//
// logger.info("Number of supply bids: " + supplyBids.size() + " and demand: " + demandBids.size());
//
// if (supplyBids.size() == 0 || demandBids.size() == 0) {
// logger.info("Either no supply bids or no demand bids - supply: {}; demand: {}", +supplyBids.size(), demandBids.size());
// return null;
// } else {
// logger.info("{} supply bids and {} demand bids present on " + market, supplyBids.size(), demandBids.size());
// }
//
// double totalSupply = 0d;
// for (Bid bid : supplyBids) {
// totalSupply += bid.getAmount();
// }
// double totalDemand = 0d;
// for (Bid bid : demandBids) {
// totalDemand += bid.getAmount();
// }
// logger.info("Total supply: {} -- total demand: {}", totalSupply, totalDemand);
//
// Collections.sort(supplyBids, new BidPriceComparator());
// Collections.sort(demandBids, new BidPriceReverseComparator());
// logger.info("Bids sorted on price");
//
// // TODO check whether there are negative amounts bid. Negative prices
// // may be ok, possibly warn. Negative amounts are not allowed.
//
// boolean settled = false;
// double price = 0d;
// double amount = 0d;
//
// double totalSupplyAccepted = 0d;
// double totalDemandAccepted = 0d;
// double lastSupplyPrice = 0d;
// double lastDemandPrice = 0d;
//
// int bidDemandIndex = 0;
// int bidSupplyIndex = 0;
//
// boolean done = false;
//
// // SUPPLY LOOP
// while (!done) {
// boolean supplyBidMet = false;
//
// if (supplyBids.size() <= bidSupplyIndex) {
// logger.info("No more supply bids");
// // no more supply bids.
// done = true;
// settled = true;
//
// amount = totalSupplyAccepted;
// if (market.isAuction()) {
// price = lastDemandPrice;
// } else {
// price = lastSupplyPrice;
// }
//
// logger.info("Accepted the last demand bid partly as a final bid");
//
// // Supply bid is accepted
// double partialAcceptance = totalSupplyAccepted - totalDemandAccepted;
//
// calculateAndDetermineSharedAcceptance(demandBids, demandBids.get(bidDemandIndex), partialAcceptance);
//
// totalDemandAccepted += partialAcceptance;
//
// logger.info("Cleared: partial demand bid, no more supply bids.");
//
// } else {
// double supplyAmount = supplyBids.get(bidSupplyIndex).getAmount();
// double supplyPrice = supplyBids.get(bidSupplyIndex).getPrice();
//
// // DEMAND LOOP
// while (!supplyBidMet && !done) {
//
// if (demandBids.size() <= bidDemandIndex) {
// // no more demand bids;
// logger.info("No more demand bids, settle with accepted demand so far. Maybe a partial supply bid.");
// done = true;
//
// if ((totalDemandAccepted) > (totalSupplyAccepted)) {
//
// settled = true;
// logger.info("Accepted a partial supply bid as a final bid");
// logger.info("Accepted a demand bid as a final bid");
//
// demandBids.get(bidDemandIndex - 1).updateStatus(Bid.ACCEPTED);
//
// double partialAcceptance = totalDemandAccepted - totalSupplyAccepted;
//
// calculateAndDetermineSharedAcceptance(supplyBids, supplyBids.get(bidSupplyIndex), partialAcceptance);
//
// logger.info("Supply bid part accepted is " + partialAcceptance);
// totalSupplyAccepted += partialAcceptance;
// price = supplyPrice;
// amount = totalSupplyAccepted;
// logger.info("Done case 2: partial supply bid.");
// }
//
// } else {
// double demandAmount = demandBids.get(bidDemandIndex).getAmount();
// double demandPrice = demandBids.get(bidDemandIndex).getPrice();
// lastDemandPrice = demandPrice;
//
// // logger.info("Demand price: " + demandPrice);
//
// // Should this demand bid be accepted? If true, the
// // demand bid is still above the supply bid on the bid
// // ladder.
//
// if (supplyPrice <= demandPrice) {
//
// // Is this demand bid is smaller in amount than the
// // supply bid including the current one?
// if ((totalDemandAccepted + demandAmount) < (totalSupplyAccepted + supplyAmount)) {
//
// logger.info("Accepted a demand bid");
// // Demand bid is accepted
// demandBids.get(bidDemandIndex).updateStatus(Bid.ACCEPTED);
// // lastDemandPrice = demandPrice;
// totalDemandAccepted += demandAmount;
// bidDemandIndex++;
//
// } else if ((totalDemandAccepted + demandAmount) == (totalSupplyAccepted + supplyAmount)) {
//
// logger.info("Accepted both a demand and a supply bid");
// // Demand and supply bids are accepted
// logger.info("Bid repository {}", getReps().bidRepository);
// demandBids.get(bidDemandIndex).updateStatus(Bid.ACCEPTED);
// supplyBids.get(bidSupplyIndex).updateStatus(Bid.ACCEPTED);
// // lastDemandPrice = demandPrice;
// lastSupplyPrice = supplyPrice;
// totalDemandAccepted += demandAmount;
// totalSupplyAccepted += supplyAmount;
// bidDemandIndex++;
// bidSupplyIndex++;
//
// } else {
//
// // this demand bid is larger then the supply
// // bid, we should check the next supply bid and
// // leave the demand bid as it is
// logger.info("Accepted a supply bid");
// // Supply bid is accepted
// supplyBids.get(bidSupplyIndex).updateStatus(Bid.ACCEPTED);
// supplyBidMet = true; // We go out the demand
// // loop
// bidSupplyIndex++;
// lastSupplyPrice = supplyPrice;
// totalSupplyAccepted += supplyAmount;
// }
// } else {
// logger.info("Demand curve now below supply curve, so the clearing has been passed");
// done = true;
//
// if (totalDemandAccepted == totalSupplyAccepted) {
// // We have not settled yet!
// logger.info("We are in balance. Settle later.");
// } else {
// if ((totalDemandAccepted + demandAmount) > (totalSupplyAccepted)) {
//
// settled = true;
// // this demand bid is larger then the supply
// // bid, we should check the next supply bid
// // and leave the demand bid as it is
// logger.info("Accepted a partial demand bid as a final bid");
// // Supply bid is accepted
//
// supplyBids.get(bidSupplyIndex).updateStatus(Bid.FAILED);
// // lastDemandPrice = demandPrice;
// double partialAcceptance = totalSupplyAccepted - totalDemandAccepted;
//
// calculateAndDetermineSharedAcceptance(demandBids, demandBids.get(bidDemandIndex), partialAcceptance);
//
// logger.info("Demand bid part accepted is " + partialAcceptance);
// totalDemandAccepted += partialAcceptance;
// price = demandPrice;
// amount = totalDemandAccepted;
// logger.info("Done case 3: partial demand bid.");
// }
// }
//
// }
//
// }
// if (done && !settled) {
//
// // supply is now larger then demand. We're done
// // somewhere in
// // this area. With, without this bid, or something.
//
// // Found a price and a demand
// // logger.info("Done but not yet settled");
// // logger.info("Total supply: " + totalSupplyAccepted);
// // logger.info("Total demand: " + totalDemandAccepted);
//
// // Case 1. We're exactly on the crossing
// if (totalDemandAccepted == totalSupplyAccepted) {
// logger.info("Done case 4: even bid.");
// amount = totalSupplyAccepted;
// price = lastSupplyPrice;
// supplyBidMet = true;
// }
// }
// }
//
// }
// }
// logger.info("Cleared with amount: " + amount + " and price: " + price);
//
// ClearingPoint clearingPoint = getReps().clearingPointRepositoryOld.createOrUpdateClearingPoint(market, price, amount, time);
//
// // Setting all bids that are still Submitted to Failed
// for (Bid bid : demandBids) {
// if (bid.getStatus() == Bid.SUBMITTED) {
// bid.updateStatus(Bid.FAILED);
// }
// }
// for (Bid bid : supplyBids) {
// if (bid.getStatus() == Bid.SUBMITTED) {
// bid.updateStatus(Bid.FAILED);
// }
// }
// logger.info("Set other bids to failed");
// return clearingPoint;
// }
//
// private void calculateAndDetermineSharedAcceptance(List<Bid> bids, Bid bid, double partialAcceptance) {
// // check whether there are more demand bids that match the same
// // price level and make them all accept part
// ArrayList<Bid> bidsThatAreAllToBePartial = getBidsWithSamePrice(bids, bid);
// double ratio = partialAcceptance / getTotalVolumeOfBids(bidsThatAreAllToBePartial);
// for (Bid _bid : bidsThatAreAllToBePartial) {
// _bid.updateStatus(Bid.PARTLY_ACCEPTED);
// overrideBidAmount(_bid, ratio);
// }
//
// logger.info("For " + bidsThatAreAllToBePartial.size() + " demand bid(s) they are partly accepted, total " + partialAcceptance
// + ", ratio: " + ratio);
//
// }
//
// private void overrideBidAmount(Bid bid, double ratio) {
// bid.updateAmount(bid.getAmount() * ratio);
// }
//
// private ArrayList<Bid> getBidsWithSamePrice(List<Bid> bids, Bid bidToMatch) {
// logger.info("Finding bids in a list with the same price as some bid");
// ArrayList<Bid> bidsThatMatch = new ArrayList<Bid>();
//
// for (Bid bid : bids) {
// if (bid.getPrice() == bidToMatch.getPrice()) {
// bidsThatMatch.add(bid);
// }
// }
// return bidsThatMatch;
// }
//
// private double getTotalVolumeOfBids(List<Bid> bids) {
// logger.info("Calculating volumes of a number of bids");
// double volume = 0d;
// for (Bid bid : bids) {
// volume += bid.getAmount();
// }
// return volume;
// }
//
// //
// // public SegmentClearingPoint
// // calculateSegmentClearingPoint(DecarbonizationMarket market,
// // Iterable<ElectricitySpotBid> supplyBidsIterable,
// // Iterable<ElectricitySpotBid> demandBidsIterable, long time, Segment
// // segment) {
// //
// // List<Bid> demandBids = Utils.asDownCastedList(demandBidsIterable);
// // List<Bid> supplyBids = Utils.asDownCastedList(supplyBidsIterable);
// //
// // ClearingPoint clearingPoint = calculateClearingPoint(market, supplyBids,
// // demandBids, time);
// //
// // if (clearingPoint != null) {
// // SegmentClearingPoint segmentClearingPoint =
// // getClearingPointRepository().createOrUpdateSegmentClearingPoint(segment,
// // clearingPoint.getAbstractMarket(), clearingPoint.getPrice(),
// // clearingPoint.getVolume(), clearingPoint.getTime(),
// // clearingPoint.getIteration());
// //
// // return segmentClearingPoint;
// // } else {
// // return null;
// // }
// //
// // }
}