org.powertac.samplebroker.MarketManagerService.java Source code

Java tutorial

Introduction

Here is the source code for org.powertac.samplebroker.MarketManagerService.java

Source

/*
 * Copyright (c) 2012-2014 by the original author
 *
 * 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 org.powertac.samplebroker;

import java.util.HashMap;
import java.util.Random;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.powertac.common.BalancingTransaction;
import org.powertac.common.CapacityTransaction;
import org.powertac.common.ClearedTrade;
import org.powertac.common.Competition;
import org.powertac.common.DistributionTransaction;
import org.powertac.common.MarketPosition;
import org.powertac.common.MarketTransaction;
import org.powertac.common.Order;
import org.powertac.common.Orderbook;
import org.powertac.common.Timeslot;
import org.powertac.common.WeatherForecast;
import org.powertac.common.WeatherReport;
import org.powertac.common.config.ConfigurableValue;
import org.powertac.common.msg.BalanceReport;
import org.powertac.common.msg.MarketBootstrapData;
import org.powertac.common.repo.TimeslotRepo;
import org.powertac.samplebroker.core.BrokerPropertiesService;
import org.powertac.samplebroker.interfaces.Activatable;
import org.powertac.samplebroker.interfaces.BrokerContext;
import org.powertac.samplebroker.interfaces.Initializable;
import org.powertac.samplebroker.interfaces.MarketManager;
import org.powertac.samplebroker.interfaces.PortfolioManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Handles market interactions on behalf of the broker.
 * @author John Collins
 */
@Service
public class MarketManagerService implements MarketManager, Initializable, Activatable {
    static private Logger log = LogManager.getLogger(MarketManagerService.class);

    private BrokerContext broker; // broker

    // Spring fills in Autowired dependencies through a naming convention
    @Autowired
    private BrokerPropertiesService propertiesService;

    @Autowired
    private TimeslotRepo timeslotRepo;

    @Autowired
    private PortfolioManager portfolioManager;

    // ------------ Configurable parameters --------------
    // max and min offer prices. Max means "sure to trade"
    @ConfigurableValue(valueType = "Double", description = "Upper end (least negative) of bid price range")
    private double buyLimitPriceMax = -1.0; // broker pays

    @ConfigurableValue(valueType = "Double", description = "Lower end (most negative) of bid price range")
    private double buyLimitPriceMin = -70.0; // broker pays

    @ConfigurableValue(valueType = "Double", description = "Upper end (most positive) of ask price range")
    private double sellLimitPriceMax = 70.0; // other broker pays

    @ConfigurableValue(valueType = "Double", description = "Lower end (least positive) of ask price range")
    private double sellLimitPriceMin = 0.5; // other broker pays

    @ConfigurableValue(valueType = "Double", description = "Minimum bid/ask quantity in MWh")
    private double minMWh = 0.001; // don't worry about 1 KWh or less

    @ConfigurableValue(valueType = "Integer", description = "If set, seed the random generator")
    private Integer seedNumber = null;

    // ---------------- local state ------------------
    private Random randomGen; // to randomize bid/ask prices

    // Bid recording
    private HashMap<Integer, Order> lastOrder;
    private double[] marketMWh;
    private double[] marketPrice;
    private double meanMarketPrice = 0.0;

    public MarketManagerService() {
        super();
    }

    /* (non-Javadoc)
     * @see org.powertac.samplebroker.MarketManager#init(org.powertac.samplebroker.SampleBroker)
     */
    @Override
    public void initialize(BrokerContext broker) {
        this.broker = broker;
        lastOrder = new HashMap<>();
        propertiesService.configureMe(this);
        System.out.println("  name=" + broker.getBrokerUsername());
        if (seedNumber != null) {
            System.out.println("  seeding=" + seedNumber);
            log.info("Seeding with : " + seedNumber);
            randomGen = new Random(seedNumber);
        } else {
            randomGen = new Random();
        }
    }

    // ----------------- data access -------------------
    /**
     * Returns the mean price observed in the market
     */
    @Override
    public double getMeanMarketPrice() {
        return meanMarketPrice;
    }

    // --------------- message handling -----------------
    /**
     * Handles the Competition instance that arrives at beginning of game.
     * Here we capture minimum order size to avoid running into the limit
     * and generating unhelpful error messages.
     */
    public synchronized void handleMessage(Competition comp) {
        minMWh = Math.max(minMWh, comp.getMinimumOrderQuantity());
    }

    /**
     * Handles a BalancingTransaction message.
     */
    public synchronized void handleMessage(BalancingTransaction tx) {
        log.info("Balancing tx: " + tx.getCharge());
    }

    /**
     * Handles a ClearedTrade message - this is where you would want to keep
     * track of market prices.
     */
    public synchronized void handleMessage(ClearedTrade ct) {
    }

    /**
     * Handles a DistributionTransaction - charges for transporting power
     */
    public synchronized void handleMessage(DistributionTransaction dt) {
        log.info("Distribution tx: " + dt.getCharge());
    }

    /**
     * Handles a CapacityTransaction - a charge for contribution to overall
     * peak demand over the recent past.
     */
    public synchronized void handleMessage(CapacityTransaction dt) {
        log.info("Capacity tx: " + dt.getCharge());
    }

    /**
     * Receives a MarketBootstrapData message, reporting usage and prices
     * for the bootstrap period. We record the overall weighted mean price,
     * as well as the mean price and usage for a week.
     */
    public synchronized void handleMessage(MarketBootstrapData data) {
        marketMWh = new double[broker.getUsageRecordLength()];
        marketPrice = new double[broker.getUsageRecordLength()];
        double totalUsage = 0.0;
        double totalValue = 0.0;
        for (int i = 0; i < data.getMwh().length; i++) {
            totalUsage += data.getMwh()[i];
            totalValue += data.getMarketPrice()[i] * data.getMwh()[i];
            if (i < broker.getUsageRecordLength()) {
                // first pass, just copy the data
                marketMWh[i] = data.getMwh()[i];
                marketPrice[i] = data.getMarketPrice()[i];
            } else {
                // subsequent passes, accumulate mean values
                int pass = i / broker.getUsageRecordLength();
                int index = i % broker.getUsageRecordLength();
                marketMWh[index] = (marketMWh[index] * pass + data.getMwh()[i]) / (pass + 1);
                marketPrice[index] = (marketPrice[index] * pass + data.getMarketPrice()[i]) / (pass + 1);
            }
        }
        meanMarketPrice = totalValue / totalUsage;
    }

    /**
     * Receives a MarketPosition message, representing our commitments on 
     * the wholesale market
     */
    public synchronized void handleMessage(MarketPosition posn) {
        broker.getBroker().addMarketPosition(posn, posn.getTimeslotIndex());
    }

    /**
     * Receives a new MarketTransaction. We look to see whether an order we
     * have placed has cleared.
     */
    public synchronized void handleMessage(MarketTransaction tx) {
        // reset price escalation when a trade fully clears.
        Order lastTry = lastOrder.get(tx.getTimeslotIndex());
        if (lastTry == null) // should not happen
            log.error("order corresponding to market tx " + tx + " is null");
        else if (tx.getMWh() == lastTry.getMWh()) // fully cleared
            lastOrder.put(tx.getTimeslotIndex(), null);
    }

    /**
     * Receives market orderbooks. These list un-cleared bids and asks,
     * from which a broker can construct approximate supply and demand curves
     * for the following timeslot.
     */
    public synchronized void handleMessage(Orderbook orderbook) {
    }

    /**
     * Receives a new WeatherForecast.
     */
    public synchronized void handleMessage(WeatherForecast forecast) {
    }

    /**
     * Receives a new WeatherReport.
     */
    public synchronized void handleMessage(WeatherReport report) {
    }

    /**
     * Receives a BalanceReport containing information about imbalance in the
     * current timeslot.
     */
    public synchronized void handleMessage(BalanceReport report) {
    }

    // ----------- per-timeslot activation ---------------

    /**
     * Compute needed quantities for each open timeslot, then submit orders
     * for those quantities.
     *
     * @see org.powertac.samplebroker.interfaces.Activatable#activate(int)
     */
    @Override
    public synchronized void activate(int timeslotIndex) {
        double neededKWh = 0.0;
        log.debug("Current timeslot is " + timeslotRepo.currentTimeslot().getSerialNumber());
        for (Timeslot timeslot : timeslotRepo.enabledTimeslots()) {
            int index = (timeslot.getSerialNumber()) % broker.getUsageRecordLength();
            neededKWh = portfolioManager.collectUsage(index);
            submitOrder(neededKWh, timeslot.getSerialNumber());
        }
    }

    /**
     * Composes and submits the appropriate order for the given timeslot.
     */
    private void submitOrder(double neededKWh, int timeslot) {
        double neededMWh = neededKWh / 1000.0;

        MarketPosition posn = broker.getBroker().findMarketPositionByTimeslot(timeslot);
        if (posn != null)
            neededMWh -= posn.getOverallBalance();
        if (Math.abs(neededMWh) <= minMWh) {
            log.info("no power required in timeslot " + timeslot);
            return;
        }
        Double limitPrice = computeLimitPrice(timeslot, neededMWh);
        log.info("new order for " + neededMWh + " at " + limitPrice + " in timeslot " + timeslot);
        Order order = new Order(broker.getBroker(), timeslot, neededMWh, limitPrice);
        lastOrder.put(timeslot, order);
        broker.sendMessage(order);
    }

    /**
     * Computes a limit price with a random element. 
     */
    private Double computeLimitPrice(int timeslot, double amountNeeded) {
        log.debug("Compute limit for " + amountNeeded + ", timeslot " + timeslot);
        // start with default limits
        Double oldLimitPrice;
        double minPrice;
        if (amountNeeded > 0.0) {
            // buying
            oldLimitPrice = buyLimitPriceMax;
            minPrice = buyLimitPriceMin;
        } else {
            // selling
            oldLimitPrice = sellLimitPriceMax;
            minPrice = sellLimitPriceMin;
        }
        // check for escalation
        Order lastTry = lastOrder.get(timeslot);
        if (lastTry != null)
            log.debug("lastTry: " + lastTry.getMWh() + " at " + lastTry.getLimitPrice());
        if (lastTry != null && Math.signum(amountNeeded) == Math.signum(lastTry.getMWh())) {
            oldLimitPrice = lastTry.getLimitPrice();
            log.debug("old limit price: " + oldLimitPrice);
        }

        // set price between oldLimitPrice and maxPrice, according to number of
        // remaining chances we have to get what we need.
        double newLimitPrice = minPrice; // default value
        int current = timeslotRepo.currentSerialNumber();
        int remainingTries = (timeslot - current - Competition.currentCompetition().getDeactivateTimeslotsAhead());
        log.debug("remainingTries: " + remainingTries);
        if (remainingTries > 0) {
            double range = (minPrice - oldLimitPrice) * 2.0 / (double) remainingTries;
            log.debug("oldLimitPrice=" + oldLimitPrice + ", range=" + range);
            double computedPrice = oldLimitPrice + randomGen.nextDouble() * range;
            return Math.max(newLimitPrice, computedPrice);
        } else
            return null; // market order
    }
}