import java.time.LocalDate;

import org.ojalgo.OjAlgoUtils;
import org.ojalgo.data.domain.finance.series.DataSource;
import org.ojalgo.data.domain.finance.series.YahooSession;
import org.ojalgo.netio.BasicLogger;
import org.ojalgo.random.SampleSet;
import org.ojalgo.random.process.GeometricBrownianMotion;
import org.ojalgo.random.process.RandomProcess.SimulationResults;
import org.ojalgo.series.BasicSeries;
import org.ojalgo.series.primitive.CoordinatedSet;
import org.ojalgo.series.primitive.PrimitiveSeries;
import org.ojalgo.type.CalendarDateUnit;
import org.ojalgo.type.PrimitiveNumber;

/**
 * An example demonstrating how to download historical financial time series data, and how you can continue
 * working with it.
 *
 * @see https://www.ojalgo.org/2019/04/ojalgo-v47-1-ojalgo-finance-v2-1-financial-time-series-data/
 * @see https://github.com/optimatika/ojAlgo/wiki/Financial-Data
 */
public abstract class FinancialData {

    public static void main(final String[] args) {

        BasicLogger.debug();
        BasicLogger.debug(FinancialData.class);
        BasicLogger.debug(OjAlgoUtils.getTitle());
        BasicLogger.debug(OjAlgoUtils.getDate());
        BasicLogger.debug();

        // First create the data sources

        DataSource sourceMSFT = DataSource.newAlphaVantage("MSFT", CalendarDateUnit.DAY, "demo");

        // Different Yahoo data sources should share a common session

        YahooSession yahooSession = new YahooSession();
        DataSource sourceAAPL = DataSource.newYahoo(yahooSession, "AAPL", CalendarDateUnit.DAY);
        DataSource sourceIBM = DataSource.newYahoo(yahooSession, "IBM", CalendarDateUnit.DAY);
        DataSource sourceORCL = DataSource.newYahoo(yahooSession, "ORCL", CalendarDateUnit.DAY);

        // Fetch the data

        BasicSeries<LocalDate, PrimitiveNumber> seriesIBM = sourceIBM.getLocalDateSeries(CalendarDateUnit.MONTH);
        BasicSeries<LocalDate, PrimitiveNumber> seriesORCL = sourceORCL.getLocalDateSeries(CalendarDateUnit.MONTH);

        BasicSeries<LocalDate, PrimitiveNumber> seriesMSFT = sourceMSFT.getLocalDateSeries(CalendarDateUnit.MONTH);
        BasicSeries<LocalDate, PrimitiveNumber> seriesAAPL = sourceAAPL.getLocalDateSeries(CalendarDateUnit.MONTH);

        BasicLogger.debug("Range for {} is from {} to {}", seriesMSFT.getName(), seriesMSFT.firstKey(), seriesMSFT.lastKey());
        BasicLogger.debug("Range for {} is from {} to {}", seriesAAPL.getName(), seriesAAPL.firstKey(), seriesAAPL.lastKey());
        BasicLogger.debug("Range for {} is from {} to {}", seriesIBM.getName(), seriesIBM.firstKey(), seriesIBM.lastKey());
        BasicLogger.debug("Range for {} is from {} to {}", seriesORCL.getName(), seriesORCL.firstKey(), seriesORCL.lastKey());

        // Coordinate the series - common start, end and frequency

        CoordinatedSet<LocalDate> coordinationSet = CoordinatedSet.from(seriesMSFT, seriesAAPL, seriesIBM, seriesORCL);

        BasicLogger.debug();
        BasicLogger.debug("Common range is from {} to {}", coordinationSet.getFirstKey(), coordinationSet.getLastKey());

        PrimitiveSeries primitiveMSFT = coordinationSet.getSeries(0);
        PrimitiveSeries primitiveAAPL = coordinationSet.getSeries(1);
        PrimitiveSeries primitiveIBM = coordinationSet.getSeries(2);
        PrimitiveSeries primitiveORCL = coordinationSet.getSeries(3);

        // Create sample sets of log differences

        SampleSet sampleSetMSFT = SampleSet.wrap(primitiveMSFT.log().differences());
        SampleSet sampleSetIBM = SampleSet.wrap(primitiveIBM.log().differences());

        BasicLogger.debug();
        BasicLogger.debug("Sample statistics (logarithmic differences on monthly data)");
        BasicLogger.debug("MSFT:  {}", sampleSetMSFT);
        BasicLogger.debug("IBM: {}", sampleSetIBM);
        BasicLogger.debug("Correlation: {}", sampleSetIBM.getCorrelation(sampleSetMSFT));

        // Estimate stochastic process parameters based on historical data

        GeometricBrownianMotion monthlyProcAAPL = GeometricBrownianMotion.estimate(primitiveAAPL, 1.0);
        GeometricBrownianMotion monthlyProcORCL = GeometricBrownianMotion.estimate(primitiveORCL, 1.0);
        monthlyProcAAPL.setValue(1.0); // To normalize the current value
        monthlyProcORCL.setValue(1.0);

        double yearsPerMonth = CalendarDateUnit.YEAR.convert(CalendarDateUnit.MONTH);

        // The same thing, but annualised

        GeometricBrownianMotion annualProcAAPL = GeometricBrownianMotion.estimate(primitiveAAPL, yearsPerMonth);
        GeometricBrownianMotion annualProcORCL = GeometricBrownianMotion.estimate(primitiveORCL, yearsPerMonth);
        annualProcAAPL.setValue(1.0); // To normalize the current value
        annualProcORCL.setValue(1.0);

        // Comparing the monthly and annual stochastic processes for AAPL

        BasicLogger.debug();
        BasicLogger.debug("    Apple    Monthly proc    Annual proc    (6 months from now)");
        BasicLogger.debug("Expected: {}    {}", monthlyProcAAPL.getDistribution(6.0).getExpected(), annualProcAAPL.getDistribution(0.5).getExpected());
        BasicLogger.debug("StdDev:   {}    {}", monthlyProcAAPL.getDistribution(6.0).getStandardDeviation(),
                annualProcAAPL.getDistribution(0.5).getStandardDeviation());
        BasicLogger.debug("Var:      {}    {}", monthlyProcAAPL.getDistribution(6.0).getVariance(), annualProcAAPL.getDistribution(0.5).getVariance());

        // Comparing the annualized stochastic processes for AAPL and ORCL

        BasicLogger.debug();
        BasicLogger.debug("    Apple    Oracle    (1 year from now)");
        BasicLogger.debug("Current:  {}    {}", annualProcAAPL.getValue(), annualProcORCL.getValue());
        // getExpected() is a shortcut to getDistribution(1.0).getExpected()
        BasicLogger.debug("Expected: {}    {}", annualProcAAPL.getExpected(), annualProcORCL.getExpected());
        // getStandardDeviation() is a shortcut to getDistribution(1.0).getStandardDeviation()
        BasicLogger.debug("StdDev:   {}    {}", annualProcAAPL.getStandardDeviation(), annualProcORCL.getStandardDeviation());
        // getVariance() is a shortcut to getDistribution(1.0).getVariance()
        BasicLogger.debug("Var:      {}    {}", annualProcAAPL.getVariance(), annualProcORCL.getVariance());

        // Simulate future scenarios for ORCL

        SimulationResults simulationResults = annualProcORCL.simulate(1000, 12, yearsPerMonth);
        BasicLogger.debug();
        BasicLogger.debug("Simulate future Oracle: 1000 scenarios, take 12 incremental steps of size 'yearsPerMonth' (1/12)");
        // There are 12 sample sets indexed 0 to 11
        BasicLogger.debug("Simulated sample set: {}", simulationResults.getSampleSet(11));
        // There are 1000 scenarios indexed 0 to 999
        BasicLogger.debug("Simulated scenario: {}", simulationResults.getScenario(999));

        // There is a shortcut to get a coordinated set

        DataSource.Coordinated coordinated = DataSource.coordinated(CalendarDateUnit.DAY);
        coordinated.addAlphaVantage("MSFT", "demo");
        coordinated.addYahoo("AAPL");
        coordinated.addYahoo("IBM");
        coordinated.addYahoo("ORCL");
        CoordinatedSet<LocalDate> coordinationSet2 = coordinated.get();

    }

}