/* 
 * Simple Black-Litterman Calculator
 * Created on Jan 30, 2006 By Thomas
 */
import javax.swing.JFrame;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;

import cern.colt.matrix.DoubleFactory2D;
import cern.colt.matrix.DoubleMatrix2D;
import cern.colt.matrix.impl.DenseDoubleMatrix2D;
import cern.colt.matrix.linalg.Algebra;


/**
 * The main GUI handling class.
 * @author Thomas
 * 
 */
public class SimpleBlack {

	static final String[] assets = { "US Equity", "US Bonds", "Int'l Equity" };

	static final double[][] weights = { { .382, .275, .344 } };

	static final double[][] covar = { { .036, .002, .010 },
			{ .002, .003, .001 }, { .010, .001, .025 } };

	static final double risk_premium = .04;

	static final double variance = .01117;

	static final double risk_free_rate = .045;

	public static void main(String[] args) {
		new SimpleBlack().run();
	}

	public void run() {
		showChart(makePie("Global Portfolio", new DenseDoubleMatrix2D(weights)));

		DoubleMatrix2D implied_returns = BlackLitterman.revOpt(
				new DenseDoubleMatrix2D(weights),
				new DenseDoubleMatrix2D(covar), variance, risk_premium,
				risk_free_rate);

		showChart(makeBar("Implied Returns", implied_returns));

		DoubleMatrix2D implied_alloc = BlackLitterman.equilOptCov(assets,
				new DenseDoubleMatrix2D(weights),
				new DenseDoubleMatrix2D(covar), variance, risk_premium,
				risk_free_rate, 0.60);

		showChart(makePie("Black-Litterman(Risk=0.60)", implied_alloc));
	}

	static void showChart(JFreeChart chart) {
		JFrame frame = new JFrame();
		frame.setContentPane(new ChartPanel(chart));
		frame.pack();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}

	static JFreeChart makePie(String label, DoubleMatrix2D weights) {
		DefaultPieDataset data = new DefaultPieDataset();

		for (int i = assets.length - 1; i != -1; i--) {
			data.setValue(assets[i], weights.get(0, i));
		}

		return ChartFactory.createPieChart3D(label, data, false, false, false);
	}

	static JFreeChart makeBar(String label, DoubleMatrix2D values) {
		DefaultCategoryDataset data = new DefaultCategoryDataset();

		for (int i = assets.length - 1; i != -1; i--) {
			data.addValue(values.get(0, i), assets[i], assets[i]);
		}

		return ChartFactory.createBarChart3D(label, "", "", data,
				PlotOrientation.VERTICAL, false, false, false);
	}

	/**
	 * Our actual Black-Litterman calcuations.
	 * @author thomasbarker
	 *
	 */
	private static class BlackLitterman {

		/**
		 * Forms the covariance matrix and passes it to equilOptCov.
		 */
		public static final DoubleMatrix2D equilOptCorr(String[] assets,
				DoubleMatrix2D market_ratio, DoubleMatrix2D sd,
				DoubleMatrix2D cc, double global_sd, double premium,
				double risk_free, double risk) {

			// Calculate the Covariance
			DoubleMatrix2D C = ModelUtility.calcCovariance(sd, cc);

			return equilOptCov(assets, market_ratio, C, global_sd, premium,
					risk_free, risk);
		}

		/**
		 * Forms the optimal portfolio given the returns infered by revOpt,
		 * and the investors risk-aversion.
		 */
		public static final DoubleMatrix2D equilOptCov(String[] assets,
				DoubleMatrix2D market_ratio, DoubleMatrix2D C,
				double global_sd, double premium, double risk_free, double risk) {
			DoubleMatrix2D e = revOpt(market_ratio, C, global_sd, premium,
					risk_free);

			World w = new World(assets, e, C);

			return Algebra.DEFAULT.transpose(MarkOpt.doMark(w, risk));
		}

		/**
		 * Given the ratio of market valuations, what returns would a
		 * rational representative investor expect to make.
		 */
		public static final DoubleMatrix2D revOpt(DoubleMatrix2D market_ratio,
				DoubleMatrix2D C, double global_sd, double premium,
				double risk_free) {

			// Global Risk Aversion Coefficient
			double RAC = premium / global_sd;

			// Back-Out Equilibrium Market Returns
			DoubleMatrix2D impl_returns = Algebra.DEFAULT.mult(C,
					Algebra.DEFAULT.transpose(market_ratio));
			impl_returns.assign(cern.jet.math.Functions.mult(RAC));
			impl_returns.assign(cern.jet.math.Functions.plus(risk_free));

			return Algebra.DEFAULT.transpose(impl_returns);
		}
	}

	/**
	 * Holds the Markovitz optimisation logic.
	 * This will produce optimal portfolios - provided that the normal
	 * distribution accurately models markets [it *sort* of does] and that
	 * we can predict asset returns [if you can, tell me how!].
	 * @author thomasbarker
	 *
	 */
	public static final class MarkOpt {

		/**
		 * Optimises total utility where returns are good and volitility is bad.
		 * This is actually a L-P problem, but for this simple case we can skip
		 * the full L-P iteraterating to a solution thing.  So instead we just have
		 * some very tortured matrix math.
		 */
		public static final DoubleMatrix2D doMark(World w, double risk) {

			// Set-up the Problem
			DoubleMatrix2D D = DoubleFactory2D.dense.appendRows(
					DoubleFactory2D.dense.appendColumns(w.getAssetCovariance()
							.copy().assign(cern.jet.math.Functions.mult(2)),
							DoubleFactory2D.dense.make(w.size() + 1, 1, 1.0)),
					DoubleFactory2D.dense.make(1, w.size() + 1, 1.0));
			D.set(D.columns() - 1, D.columns() - 1, 0.0);

			DoubleMatrix2D F = DoubleFactory2D.dense.appendRows(Algebra.DEFAULT
					.transpose(w.getAssetReturns()), DoubleFactory2D.dense
					.make(1, 1, 0));
			DoubleMatrix2D K = DoubleFactory2D.dense.make(w.size() + 1, 1, 0.0);
			K.set(w.size(), 0, 1.0);

			// Solve the allocation
			DoubleMatrix2D zp = Algebra.DEFAULT.mult(
					Algebra.DEFAULT.inverse(D), K);

			DoubleMatrix2D rtswap = Algebra.DEFAULT.mult(Algebra.DEFAULT
					.inverse(D), F);

			rtswap.assign(cern.jet.math.Functions.mult(risk));
			zp.assign(rtswap, cern.jet.math.Functions.plus);

			return zp.viewPart(0, 0, zp.rows() - 1, 1);
		}
	}

	/**
	 * Holds the state of our financial world.
	 * @author thomasbarker
	 *
	 */
	public static final class World {
		// The names of the assets we can invest in.
		final private String[] assets;

		// Our expected returns on each asset.
		final private DoubleMatrix2D returns;

        // The matrix of covariance between out asset returns.
		final private DoubleMatrix2D covariance;

		// How many assets in our world ?
		final private int size;

		public World(final String[] assets, final DoubleMatrix2D returns,
				final DoubleMatrix2D covariance) {
			this.assets = assets;
			this.returns = returns;
			this.covariance = covariance;

			this.size = assets.length;
		}

		public World(final String[] assets, final double[][] returns,
				final double[][] covariance) {
			this(assets, new DenseDoubleMatrix2D(returns),
					new DenseDoubleMatrix2D(covariance));
		}

		public String[] getAssets() {
			return this.assets;
		}

		public DoubleMatrix2D getAssetReturns() {
			return this.returns;
		}

		public DoubleMatrix2D getAssetCovariance() {
			return this.covariance;
		}

		public int size() {
			return this.size;
		}
	}

	/**
	 * Simple utility method which forms the covariance matrix
	 * from the standard deviations and the correlations.
	 * @author thomasbarker
	 *
	 */
	public static class ModelUtility {
		public static DoubleMatrix2D calcCovariance(DoubleMatrix2D sd,
				DoubleMatrix2D cc) {
			// Calculate the Covariances
			DoubleMatrix2D C = Algebra.DEFAULT.mult(Algebra.DEFAULT
					.transpose(sd), sd);
			C.assign(cc, cern.jet.math.Functions.mult);

			return C;
		}
	}
}
