Esempio n. 1
0
        public void testDeltaPriceConsistency()
        {
            // Testing premium-adjusted delta price consistency

            // This function tests for price consistencies with the standard
            // Black Scholes calculator, since premium adjusted deltas can be calculated
            // from spot deltas by adding/subtracting the premium.

            SavedSettings backup = new SavedSettings();

            // actually, value and tol won't be needed for testing
            EuropeanOptionData[] values =
            {
                //        type, strike,   spot,    rd,    rf,    t,  vol,   value,    tol
                new EuropeanOptionData(Option.Type.Call, 0.9123, 1.2212, 0.0231, 0.0000, 0.25, 0.301, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Call, 0.9234, 1.2212, 0.0231, 0.0000, 0.35, 0.111, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Call, 0.9783, 1.2212, 0.0231, 0.0000, 0.45, 0.071, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Call, 1.0000, 1.2212, 0.0231, 0.0000, 0.55, 0.082, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Call, 1.1230, 1.2212, 0.0231, 0.0000, 0.65, 0.012, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Call, 1.2212, 1.2212, 0.0231, 0.0000, 0.75, 0.129, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Call, 1.3212, 1.2212, 0.0231, 0.0000, 0.85, 0.034, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Call, 1.3923, 1.2212, 0.0131, 0.2344, 0.95, 0.001, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Call, 1.3455, 1.2212, 0.0000, 0.0000, 1.00, 0.127, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Put,  0.9123, 1.2212, 0.0231, 0.0000, 0.25, 0.301, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Put,  0.9234, 1.2212, 0.0231, 0.0000, 0.35, 0.111, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Put,  0.9783, 1.2212, 0.0231, 0.0000, 0.45, 0.071, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Put,  1.0000, 1.2212, 0.0231, 0.0000, 0.55, 0.082, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Put,  1.1230, 1.2212, 0.0231, 0.0000, 0.65, 0.012, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Put,  1.2212, 1.2212, 0.0231, 0.0000, 0.75, 0.129, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Put,  1.3212, 1.2212, 0.0231, 0.0000, 0.85, 0.034, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Put,  1.3923, 1.2212, 0.0131, 0.2344, 0.95, 0.001, 0.0, 0.0),
                new EuropeanOptionData(Option.Type.Put,  1.3455, 1.2212, 0.0000, 0.0000, 1.00, 0.127, 0.0, 0.0),
                // extreme case: zero vol
                new EuropeanOptionData(Option.Type.Put,  1.3455, 1.2212, 0.0000, 0.0000, 0.50, 0.000, 0.0, 0.0),
                // extreme case: zero strike
                new EuropeanOptionData(Option.Type.Put,  0.0000, 1.2212, 0.0000, 0.0000, 1.50, 0.133, 0.0, 0.0),
                // extreme case: zero strike+zero vol
                new EuropeanOptionData(Option.Type.Put,  0.0000, 1.2212, 0.0000, 0.0000, 1.00, 0.133, 0.0, 0.0),
            };

            DayCounter dc       = new Actual360();
            Calendar   calendar = new TARGET();
            Date       today    = Date.Today;

            // Start setup of market data

            double discFor       = 0.0;
            double discDom       = 0.0;
            double implVol       = 0.0;
            double expectedVal   = 0.0;
            double calculatedVal = 0.0;
            double error         = 0.0;

            SimpleQuote    spotQuote  = new SimpleQuote(0.0);
            Handle <Quote> spotHandle = new Handle <Quote>(spotQuote);

            SimpleQuote        qQuote  = new SimpleQuote(0.0);
            Handle <Quote>     qHandle = new Handle <Quote>(qQuote);
            YieldTermStructure qTS     = new FlatForward(today, qHandle, dc);

            SimpleQuote        rQuote  = new SimpleQuote(0.0);
            Handle <Quote>     rHandle = new Handle <Quote>(qQuote);
            YieldTermStructure rTS     = new FlatForward(today, rHandle, dc);

            SimpleQuote           volQuote  = new SimpleQuote(0.0);
            Handle <Quote>        volHandle = new Handle <Quote>(volQuote);
            BlackVolTermStructure volTS     = new BlackConstantVol(today, calendar, volHandle, dc);

            BlackScholesMertonProcess stochProcess;
            IPricingEngine            engine;
            StrikedTypePayoff         payoff;
            Date     exDate;
            Exercise exercise;
            // Setup of market data finished

            double tolerance = 1.0e-10;

            for (int i = 0; i < values.Length; ++i)
            {
                payoff   = new PlainVanillaPayoff(values[i].type, values[i].strike);
                exDate   = today + timeToDays(values[i].t);
                exercise = new EuropeanExercise(exDate);

                spotQuote.setValue(values[i].s);
                volQuote.setValue(values[i].v);
                rQuote.setValue(values[i].r);
                qQuote.setValue(values[i].q);

                discDom = rTS.discount(exDate);
                discFor = qTS.discount(exDate);
                implVol = Math.Sqrt(volTS.blackVariance(exDate, 0.0));

                BlackDeltaCalculator myCalc = new BlackDeltaCalculator(values[i].type, DeltaVolQuote.DeltaType.PaSpot,
                                                                       spotQuote.value(), discDom, discFor, implVol);

                stochProcess = new BlackScholesMertonProcess(spotHandle,
                                                             new Handle <YieldTermStructure>(qTS),
                                                             new Handle <YieldTermStructure>(rTS),
                                                             new Handle <BlackVolTermStructure>(volTS));

                engine = new AnalyticEuropeanEngine(stochProcess);

                EuropeanOption option = new EuropeanOption(payoff, exercise);
                option.setPricingEngine(engine);

                calculatedVal = myCalc.deltaFromStrike(values[i].strike);
                expectedVal   = option.delta() - option.NPV() / spotQuote.value();
                error         = Math.Abs(expectedVal - calculatedVal);

                if (error > tolerance)
                {
                    QAssert.Fail("\n Premium-adjusted spot delta test failed. \n"
                                 + "Calculated Delta: " + calculatedVal + "\n"
                                 + "Expected Value:   " + expectedVal + "\n"
                                 + "Error: " + error);
                }

                myCalc.setDeltaType(DeltaVolQuote.DeltaType.PaFwd);

                calculatedVal = myCalc.deltaFromStrike(values[i].strike);
                expectedVal   = expectedVal / discFor; // Premium adjusted Fwd Delta is PA spot without discount
                error         = Math.Abs(expectedVal - calculatedVal);

                if (error > tolerance)
                {
                    QAssert.Fail("\n Premium-adjusted forward delta test failed. \n"
                                 + "Calculated Delta: " + calculatedVal + "\n"
                                 + "Expected Value:   " + expectedVal + "\n"
                                 + "Error: " + error);
                }


                // Test consistency with BlackScholes Calculator for Spot Delta
                myCalc.setDeltaType(DeltaVolQuote.DeltaType.Spot);

                calculatedVal = myCalc.deltaFromStrike(values[i].strike);
                expectedVal   = option.delta();
                error         = Math.Abs(calculatedVal - expectedVal);

                if (error > tolerance)
                {
                    QAssert.Fail("\n spot delta in BlackDeltaCalculator differs from delta in BlackScholesCalculator. \n"
                                 + "Calculated Value: " + calculatedVal + "\n"
                                 + "Expected Value:   " + expectedVal + "\n"
                                 + "Error: " + error);
                }
            }
        }
Esempio n. 2
0
        public void testPutCallParity()
        {
            // Testing put-call parity for deltas

            // Test for put call parity between put and call deltas.

            SavedSettings backup = new SavedSettings();

            /* The data below are from
             * "Option pricing formulas", E.G. Haug, McGraw-Hill 1998
             * pag 11-16
             */

            EuropeanOptionData[] values =
            {
                // pag 2-8
                //        type, strike,   spot,    q,    r,    t,  vol,   value,    tol
                new EuropeanOptionData(Option.Type.Call,  65.00,  60.00, 0.00, 0.08, 0.25, 0.30,  2.1334, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,   95.00, 100.00, 0.05, 0.10, 0.50, 0.20,  2.4648, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,   19.00,  19.00, 0.10, 0.10, 0.75, 0.28,  1.7011, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call,  19.00,  19.00, 0.10, 0.10, 0.75, 0.28,  1.7011, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call,   1.60,   1.56, 0.08, 0.06, 0.50, 0.12,  0.0291, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,   70.00,  75.00, 0.05, 0.10, 0.50, 0.35,  4.0870, 1.0e-4),
                // pag 24
                new EuropeanOptionData(Option.Type.Call, 100.00,  90.00, 0.10, 0.10, 0.10, 0.15,  0.0205, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.15,  1.8734, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.15,  9.9413, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00,  90.00, 0.10, 0.10, 0.10, 0.25,  0.3150, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.25,  3.1217, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.25, 10.3556, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00,  90.00, 0.10, 0.10, 0.10, 0.35,  0.9474, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.35,  4.3693, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.35, 11.1381, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00,  90.00, 0.10, 0.10, 0.50, 0.15,  0.8069, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.15,  4.0232, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.15, 10.5769, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00,  90.00, 0.10, 0.10, 0.50, 0.25,  2.7026, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.25,  6.6997, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.25, 12.7857, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00,  90.00, 0.10, 0.10, 0.50, 0.35,  4.9329, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.35,  9.3679, 1.0e-4),
                new EuropeanOptionData(Option.Type.Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.35, 15.3086, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00,  90.00, 0.10, 0.10, 0.10, 0.15,  9.9210, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.10, 0.15,  1.8734, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.10, 0.15,  0.0408, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00,  90.00, 0.10, 0.10, 0.10, 0.25, 10.2155, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.10, 0.25,  3.1217, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.10, 0.25,  0.4551, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00,  90.00, 0.10, 0.10, 0.10, 0.35, 10.8479, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.10, 0.35,  4.3693, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.10, 0.35,  1.2376, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00,  90.00, 0.10, 0.10, 0.50, 0.15, 10.3192, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.50, 0.15,  4.0232, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.50, 0.15,  1.0646, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00,  90.00, 0.10, 0.10, 0.50, 0.25, 12.2149, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.50, 0.25,  6.6997, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.50, 0.25,  3.2734, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00,  90.00, 0.10, 0.10, 0.50, 0.35, 14.4452, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 100.00, 0.10, 0.10, 0.50, 0.35,  9.3679, 1.0e-4),
                new EuropeanOptionData(Option.Type.Put,  100.00, 110.00, 0.10, 0.10, 0.50, 0.35,  5.7963, 1.0e-4),
                // pag 27
                new EuropeanOptionData(Option.Type.Call,  40.00,  42.00, 0.08, 0.04, 0.75, 0.35,  5.0975, 1.0e-4)
            };

            DayCounter dc       = new Actual360();
            Calendar   calendar = new TARGET();
            Date       today    = Date.Today;

            double discFor        = 0.0;
            double discDom        = 0.0;
            double implVol        = 0.0;
            double deltaCall      = 0.0;
            double deltaPut       = 0.0;
            double expectedDiff   = 0.0;
            double calculatedDiff = 0.0;
            double error          = 0.0;
            double forward        = 0.0;

            SimpleQuote spotQuote = new SimpleQuote(0.0);

            SimpleQuote        qQuote  = new SimpleQuote(0.0);
            Handle <Quote>     qHandle = new Handle <Quote>(qQuote);
            YieldTermStructure qTS     = new FlatForward(today, qHandle, dc);

            SimpleQuote        rQuote  = new SimpleQuote(0.0);
            Handle <Quote>     rHandle = new Handle <Quote>(qQuote);
            YieldTermStructure rTS     = new FlatForward(today, rHandle, dc);

            SimpleQuote           volQuote  = new SimpleQuote(0.0);
            Handle <Quote>        volHandle = new Handle <Quote>(volQuote);
            BlackVolTermStructure volTS     = new BlackConstantVol(today, calendar, volHandle, dc);

            StrikedTypePayoff payoff;
            Date     exDate;
            Exercise exercise;

            double tolerance = 1.0e-10;

            for (int i = 0; i < values.Length; ++i)
            {
                payoff   = new PlainVanillaPayoff(Option.Type.Call, values[i].strike);
                exDate   = today + timeToDays(values[i].t);
                exercise = new EuropeanExercise(exDate);

                spotQuote.setValue(values[i].s);
                volQuote.setValue(values[i].v);
                rQuote.setValue(values[i].r);
                qQuote.setValue(values[i].q);
                discDom = rTS.discount(exDate);
                discFor = qTS.discount(exDate);
                implVol = Math.Sqrt(volTS.blackVariance(exDate, 0.0));
                forward = spotQuote.value() * discFor / discDom;

                BlackDeltaCalculator myCalc = new BlackDeltaCalculator(Option.Type.Call, DeltaVolQuote.DeltaType.Spot,
                                                                       spotQuote.value(), discDom, discFor, implVol);

                deltaCall = myCalc.deltaFromStrike(values[i].strike);
                myCalc.setOptionType(Option.Type.Put);
                deltaPut = myCalc.deltaFromStrike(values[i].strike);
                myCalc.setOptionType(Option.Type.Call);

                expectedDiff   = discFor;
                calculatedDiff = deltaCall - deltaPut;
                error          = Math.Abs(expectedDiff - calculatedDiff);

                if (error > tolerance)
                {
                    QAssert.Fail("\n Put-call parity failed for spot delta. \n"
                                 + "Calculated Call Delta: " + deltaCall + "\n"
                                 + "Calculated Put Delta:  " + deltaPut + "\n"
                                 + "Expected Difference:   " + expectedDiff + "\n"
                                 + "Calculated Difference: " + calculatedDiff);
                }
                myCalc.setDeltaType(DeltaVolQuote.DeltaType.Fwd);

                deltaCall = myCalc.deltaFromStrike(values[i].strike);
                myCalc.setOptionType(Option.Type.Put);
                deltaPut = myCalc.deltaFromStrike(values[i].strike);
                myCalc.setOptionType(Option.Type.Call);

                expectedDiff   = 1.0;
                calculatedDiff = deltaCall - deltaPut;
                error          = Math.Abs(expectedDiff - calculatedDiff);

                if (error > tolerance)
                {
                    QAssert.Fail("\n Put-call parity failed for forward delta. \n"
                                 + "Calculated Call Delta: " + deltaCall + "\n"
                                 + "Calculated Put Delta:  " + deltaPut + "\n"
                                 + "Expected Difference:   " + expectedDiff + "\n"
                                 + "Calculated Difference: " + calculatedDiff);
                }

                myCalc.setDeltaType(DeltaVolQuote.DeltaType.PaSpot);

                deltaCall = myCalc.deltaFromStrike(values[i].strike);
                myCalc.setOptionType(Option.Type.Put);
                deltaPut = myCalc.deltaFromStrike(values[i].strike);
                myCalc.setOptionType(Option.Type.Call);

                expectedDiff   = discFor * values[i].strike / forward;
                calculatedDiff = deltaCall - deltaPut;
                error          = Math.Abs(expectedDiff - calculatedDiff);

                if (error > tolerance)
                {
                    QAssert.Fail("\n Put-call parity failed for premium-adjusted spot delta. \n"
                                 + "Calculated Call Delta: " + deltaCall + "\n"
                                 + "Calculated Put Delta:  " + deltaPut + "\n"
                                 + "Expected Difference:   " + expectedDiff + "\n"
                                 + "Calculated Difference: " + calculatedDiff);
                }

                myCalc.setDeltaType(DeltaVolQuote.DeltaType.PaFwd);

                deltaCall = myCalc.deltaFromStrike(values[i].strike);
                myCalc.setOptionType(Option.Type.Put);
                deltaPut = myCalc.deltaFromStrike(values[i].strike);
                myCalc.setOptionType(Option.Type.Call);

                expectedDiff   = values[i].strike / forward;
                calculatedDiff = deltaCall - deltaPut;
                error          = Math.Abs(expectedDiff - calculatedDiff);

                if (error > tolerance)
                {
                    QAssert.Fail("\n Put-call parity failed for premium-adjusted forward delta. \n"
                                 + "Calculated Call Delta: " + deltaCall + "\n"
                                 + "Calculated Put Delta:  " + deltaPut + "\n"
                                 + "Expected Difference:   " + expectedDiff + "\n"
                                 + "Calculated Difference: " + calculatedDiff);
                }
            }
        }
Esempio n. 3
0
        static void Main(string[] args)
        {
            /*
             * TODO:
             *  FIXES
             *      1. WAC vs Net Coupon (meanings are reversed, names are bad)
             *      2. CashFlows needs to be replaced with Expected CashFlows to get the correct price
             *  NEW IMPLEMENTATION
             *      1. Add delay
             *      2. Add SecType enum PT, PO, IO
             */

            Date referenceDate = new Date(16, 11, 2015);

            Settings.setEvaluationDate(referenceDate);
            int                   settlementDays    = 0;
            Calendar              calendar          = new TARGET();
            int                   origTerm          = 360;
            Frequency             sinkingFrequency  = Frequency.Monthly;
            DayCounter            accrualDayCounter = new Thirty360();
            BusinessDayConvention paymentConvention = BusinessDayConvention.Unadjusted;


            double wac          = 0.03875;
            int    wam          = 357;
            int    wala         = origTerm - wam;
            Date   factorDate   = new Date(1, 12, 2015);
            Date   issueDate    = calendar.advance(factorDate, -wala, TimeUnit.Months, BusinessDayConvention.Unadjusted);
            double factor       = 1.0;
            double currentFace  = 1000000;
            double originalFace = currentFace / factor;
            int    statedDelay  = 30; //54;
            double netCoupon    = 0.030;
            string secType      = "PT";
            Date   settleDate   = referenceDate;

            double yield_be = 0.0270;
            //double price;


            double speed = 0.08;

            IPrepayModel prepaymodel = new ConstantCPR(speed);
            //IPrepayModel prepaymodel = new PSACurve(factorDate, speed);

            MBSFixedRateBond mbs = new MBSFixedRateBond(
                settlementDays,
                calendar,
                currentFace,
                factorDate,
                new Period(wam, TimeUnit.Months),
                new Period(origTerm, TimeUnit.Months),
                sinkingFrequency,
                wac,
                netCoupon,
                accrualDayCounter,
                prepaymodel,
                paymentConvention,
                issueDate);

            YieldTermStructure discountCurve = new FlatForward(referenceDate, yield_be, new Thirty360(), Compounding.Compounded, Frequency.Semiannual);

            DiscountingBondEngine discountingBondEngine = new DiscountingBondEngine(new Handle <YieldTermStructure>(discountCurve));

            mbs.setPricingEngine(discountingBondEngine);

            // display results
            Console.WriteLine("WAC         : {0:F5}", wac);
            Console.WriteLine("WALA        : {0}", wala);
            Console.WriteLine("WAM         : {0}", wam);
            Console.WriteLine("Factor Date : {0}", factorDate.ToShortDateString());
            Console.WriteLine("Factor      : {0:F10}", factor);
            Console.WriteLine("Orig Face   : {0:N}", originalFace);
            Console.WriteLine("Curr Face   : {0:N}", currentFace);
            Console.WriteLine("Stated Delay: {0}", statedDelay);
            Console.WriteLine("Net Coupon  : {0:F3}", netCoupon);
            Console.WriteLine("Sec Type    : {0}", secType);
            Console.WriteLine("Settle Date : {0}", settleDate.ToShortDateString());
            Console.WriteLine("Model Type  : {0}", prepaymodel.GetType().ToString());
            Console.WriteLine("Model Speed : {0:F3}", speed);
            Console.WriteLine("Yield       : {0:F5}", yield_be);

            Console.WriteLine("Clean Price : {0:F6}", mbs.cleanPrice());
            Console.WriteLine("Dirty Price : {0:F6}", mbs.dirtyPrice());
            Console.WriteLine("Accrued     : {0:F6}", mbs.accruedAmount());

            // month, factor, pay date, ending prin, interest, reg principal, prepaid principal, total principal, net flow, cpr, smm, wala, wam, p&i payment, i payment, beg balance, days, discount, pv
            double ebal = currentFace;

            using (System.IO.StreamWriter sw = new System.IO.StreamWriter("output.csv"))
            {
                DayCounter dc      = discountCurve.dayCounter();
                Date       refdate = discountCurve.referenceDate();



                sw.WriteLine("month,factor date,factor,pay date,ending principal,interest,regular principal,prepaid principal,total principal,net flow,cpr,smm,wala,wam,p&i payment,interest payment,beginning balance,days,discount,pv");
                for (int i = 0; i < wam; i++)
                {
                    double upmt = 0;
                    double ppmt = 0;
                    double ipmt = 0;
                    double bbal = ebal;

                    Date paydate = null;

                    for (int j = 0; j <= 2; j++)
                    {
                        int      k  = i * 3 + j;
                        CashFlow cf = mbs.expectedCashflows()[k];
                        if (cf.GetType() == typeof(VoluntaryPrepay))
                        {
                            upmt    = cf.amount();
                            paydate = cf.date();
                        }
                        if (cf.GetType() == typeof(AmortizingPayment))
                        {
                            ppmt = cf.amount();
                        }
                        if (cf.GetType() == typeof(FixedRateCoupon))
                        {
                            ipmt = cf.amount();
                        }
                    }
                    int    days = dc.dayCount(refdate, paydate);
                    double df   = discountCurve.discount(paydate);
                    ebal = bbal - upmt - ppmt;
                    double smm = upmt / (bbal - ppmt);

                    sw.Write("{0},", i + 1);                                                                              //month
                    sw.Write("{0},", calendar.advance(factorDate, i, TimeUnit.Months, BusinessDayConvention.Unadjusted)); //factor date
                    sw.Write("{0:F10},", factor * bbal / currentFace);                                                    //factor
                    sw.Write("{0},", paydate.ToShortDateString());                                                        //pay date
                    sw.Write("{0:F2},", ebal);                                                                            //ending principal
                    sw.Write("{0:F2},", ipmt);                                                                            //interest
                    sw.Write("{0:F2},", ppmt);                                                                            //regular principal
                    sw.Write("{0:F2},", upmt);                                                                            //prepaid principal
                    sw.Write("{0:F2},", ppmt + upmt);                                                                     //total principal
                    sw.Write("{0:F2},", ipmt + ppmt + upmt);                                                              //net flow
                    sw.Write("{0:F4},", 1 - Math.Pow(1 - smm, 12));                                                       //cpr
                    sw.Write("{0:F6},", smm);                                                                             //smm
                    sw.Write("{0},", wala + i);                                                                           //wala
                    sw.Write("{0},", wam - i);                                                                            //wam
                    sw.Write("{0},", i);                                                                                  //p&i payment
                    sw.Write("{0},", i);                                                                                  //interest payment
                    sw.Write("{0:F2},", bbal);                                                                            //beginning balance
                    sw.Write("{0},", days);                                                                               //days
                    sw.Write("{0:F8},", df);                                                                              //discount
                    sw.WriteLine("{0}", df * (ipmt + ppmt + upmt));                                                       //pv
                }
            }
        }