/FileExtraction/Java_unseen/crisis-economics_CRISIS/CRISIS/src/eu/crisis_economics/abm/ratings/NaiveMertonDistanceToDefaultAlgorithm.java
/*
 * This file is part of CRISIS, an economics simulator.
 * 
 * Copyright (C) 2015 Ross Richardson
 * Copyright (C) 2015 John Kieran Phillips
 *
 * CRISIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * CRISIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with CRISIS.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.crisis_economics.abm.ratings;

import java.util.Map.Entry;

import org.apache.commons.math3.distribution.NormalDistribution;

import com.google.common.base.Preconditions;

import eu.crisis_economics.abm.Agent;
import eu.crisis_economics.abm.agent.SimpleAbstactAgentOperation;
import eu.crisis_economics.abm.algorithms.statistics.DiscreteTimeSeries;
import eu.crisis_economics.abm.algorithms.statistics.StdDevOfTimeSeriesStatistic;
import eu.crisis_economics.abm.contracts.Contract;
import eu.crisis_economics.abm.contracts.stocks.UniqueStockExchange;
import eu.crisis_economics.abm.simulation.Simulation;
import eu.crisis_economics.utilities.Pair;

/**
  * Compute the naive Merton 'Distance to Default' measure for an agent.
  * When applied to an agent, this measurement assumes the following:
  *   (a) that the agent is a stock releaser whose stocks are
  *       traded on the exchange, and
  *   (b) that the rating agency processing this algorithm also
  *       tracks the equity of the agent in a measurement called
  *       "Equity".
  * @author phillips
  */
public final class NaiveMertonDistanceToDefaultAlgorithm
   extends SimpleAbstactAgentOperation<Double> implements AgentTrackingMeasurement {
   
   private double forecastHorizon;
   
   public NaiveMertonDistanceToDefaultAlgorithm(final double forecastHorizon) {
      Preconditions.checkArgument(forecastHorizon > 0.);
      this.forecastHorizon = forecastHorizon;
   }
   
   /**
     * Compute the (Naive) Merton Distance-to-Default measure for an agent
     * in a specified forecast timeframe T.
     * 
     * @param debtFaceValue (F)
     *        The face value of the agent debt.
     * @param equityVolatility
     *        The volatility of agent equity.
     * @param equity (E)
     *        The current agent equity.
     * @param expectedAssetReturn
     *        The asset return of the agent during the last forecast window.
     * @param forecastHorizon (T)
     *        The period over which to forecast agent default.
     * @return
     *        A Pair<Double, Double> in the format:
     *        Pair<Naive Merton Distance-to-Default, Naive Merton
     *        Probability-of-Default>, in the period of the forecast timeframe.
     * 
     * It is not permissible for: both the debt face value (F) and equity (E)
     * aguments to be simultaneously zero; for the debt face value (F) to be
     * negative; or for the forecase horizon (T) to be zero or negative. If the 
     * debt face value is zero and equity is nonzero, then the distance to 
     * default is taken to be +Infinity.
     */
   static Pair<Double, Double> compute(
      final double debtFaceValue,
      final double equityVolatility,
      final double equity,
      final double expectedAssetReturn,
      final double forecastHorizon
      ) {
      Preconditions.checkArgument(equity != 0. || debtFaceValue > 0.);
      Preconditions.checkArgument(forecastHorizon > 0.);
      Preconditions.checkArgument(debtFaceValue >= 0.);
      final double
         debtVolatility = .05 + .25 * equityVolatility,
         overallValueVolatility =
            equityVolatility * equity / (equity + debtFaceValue) +
            debtVolatility * debtFaceValue / (equity + debtFaceValue);
      double
         distanceToDefault = Math.log((equity + debtFaceValue)/debtFaceValue) +
            (expectedAssetReturn - .5 * overallValueVolatility * overallValueVolatility) * 
               forecastHorizon;
      distanceToDefault /= Math.sqrt(forecastHorizon) * overallValueVolatility;
      NormalDistribution normalDist = new NormalDistribution();
      final double
         defaultProbability = normalDist.cumulativeProbability(-distanceToDefault);
      return Pair.create(distanceToDefault, defaultProbability);
   }
   
   @Override
   public Double operateOn(final Agent agent) {
      double
         debtFaceValue = 0.,
         equity = agent.getEquity(),
         equityVolatility = getEquityVolatility(agent),
         expectedAssetReturn = getExpectedAssetReturn(agent),
         forecastHorizon = this.forecastHorizon;
      for(final Contract contract : agent.getLiabilities())
         debtFaceValue += contract.getFaceValue();
      final double
         pDef = compute(
            debtFaceValue, equityVolatility, equity,
            expectedAssetReturn, forecastHorizon).getSecond();
      return pDef;
   }
   
   /**
     * Form a fundamentalist estimate of asset returns for the specified
     * agent. The asset return of the agent is estimated using fundamentalist
     * stock market returns in the last year (simulation time). For this 
     * reason, the agent must be stock releaser whose stocks are traded on
     * the stock exchange.
     * 
     * If stock market price per share/dividend per share information is not
     * available, or if this method is unable to estimate the stock market
     * return for the specified agent, the value 1.0 is returned.
     */
   private double getExpectedAssetReturn(final Agent agent) {
      if(!UniqueStockExchange.Instance.hasStock(agent.getUniqueName()))
         throw new IllegalArgumentException(
            "NaiveMertonDistanceToDefaultAlgorithm: agent must be a stock releaser, however the" 
          + "agent is not listed on the stock exhange.");
      final double
         timeOfInterest =
            Math.max(Simulation.getTime() - forecastHorizon, 0.);
      final DiscreteTimeSeries
         pricePerShareTimeSeries = 
            RatingAgency.Instance.getTimeSeries("Price Per Share", agent),
         dividendPerShareTimeSeries = 
            RatingAgency.Instance.getTimeSeries("Dividend Per Share", agent);
      if(pricePerShareTimeSeries.isEmpty() ||
         dividendPerShareTimeSeries.isEmpty())
         return 1.0;                                        // No information about asset returns.
      final Entry<Double, Double>
         ppsRecord = pricePerShareTimeSeries.ceilingEntry(timeOfInterest),
         dpsRecord = dividendPerShareTimeSeries.ceilingEntry(timeOfInterest);
      if(ppsRecord.getValue() == 0.)
         return 1.0;                                        // Stock market crashed.
      return dpsRecord.getValue() / ppsRecord.getValue();   // Fundamentalist return estimate.
   }
   
   /**
     * Get the standard deviation of agent equity in the last simulated
     * year. If records do not reach as far back as one year, all available
     * records are used.
     * @return
     */
   private double getEquityVolatility(final Agent agent) {
      final DiscreteTimeSeries series = RatingAgency.Instance.getTimeSeries("Equity", agent);
      if(series == null) return 0.;                         // No historical equity data.
      final double latestTimeOfInterest = 
         Math.max(Simulation.getTime() - forecastHorizon, 0.);
      DiscreteTimeSeries subseries = series.tailMap(latestTimeOfInterest, true);
      return (new StdDevOfTimeSeriesStatistic()).measureStatistic(subseries);
   }
   
   /**
     * Get the forecast horizon for this distance to default measurement.
     */
   public double getForecastHorizon() {
      return forecastHorizon;
   }
   
   /**
     * Set the forecast horizon for this distance to default measurement.
     * The forecast horizon must be strictly positive.
     */
   public void setForecastHorizon(double value) {
      Preconditions.checkArgument(value > 0.);
      this.forecastHorizon = value;
   }
   
   /**
     * Returns a brief description of this object. The exact details of the
     * string are subject to change, and should not be regarded as fixed.
     */
   @Override
   public String toString() {
      return "NaiveMertonDistanceToDefaultAlgorithm, forecast horizon: "
            + forecastHorizon + ".";
   }
}
