Sample App: rainbowPricer.js

var cc = analytics.customcolumn;
var today = cc.yymmdd(cc.calcdate());
var numSimsPerPeriod = 100;
var guiMode = false;    //set guiMode to true when testing your model in the editor

if (guiMode) {
    var portname = "BillRainbow";
    var port = positions.loadPortfolio(portname);
    var hold = port.holdings[0];
    var greeks = hold.callModelFunction(RainbowOptionModel);
}

function RainbowOptionModel(inputs) {
    log(inputs); 
    
    let outputGreeks = {};
    let spot = inputs.underlyingPrice;
    let strike = inputs.strike;
    let vol = inputs.volatility;
    let t = inputs.interestDays / 365;
    let r = inputs.domesticRate;
    let expDate = inputs.expirationDate;
    let asofDate = inputs.asofDate;
    let monteSimGenerator = analytics.createMonteModelSimGenerator("LinearCongruentialShuf");
    let numFactors = inputs.underlyingInputs.length;

    let financeInputMain = inputs.domesticFinanceCurve || inputs.domesticRate;
    
    //example of retrieving pricing inputs
    let financeInput1 = inputs.underlyingInputs[0].financeCurve || inputs.underlyingInputs[0].domesticRate;
    let divInput1 = inputs.underlyingInputs[0].dividendTable || inputs.underlyingInputs[0].dividendYield;
    let undPrice1 = inputs.underlyingInputs[0]['underlyingPrice'];
    
    //add the factors to the monteSimGenerator: 
    for (let i = 0; i < numFactors; ++i) {
        let factorSym = inputs.underlyingInputs[i].refSymbol;
        monteSimGenerator.addFactor(factorSym, "lognormal");
        monteSimGenerator.setYieldCurve(factorSym, inputs.underlyingInputs[i].financeCurve); // finance curve
        // monteSimGenerator.setBorrowCurve( factorSym, inputs.underlyingInputs[i].borrowCurve );
        // we want a constant dividend yield so we will extract the yield first and set it directly to the sim generator
        let divCurve = inputs.underlyingInputs[i].dividendTable;
        let spot = inputs.underlyingInputs[i].underlyingPrice;
        let divYield = divCurve.estimatedYield({
            startDate: asofDate,
            endDate: expDate,
            price: spot,
            yc: "USD"     
        });
        //or set the div curve directly: divYield = inputs.underlyingInputs[i].dividendTable || inputs.underlyingInputs[i].dividendYield;
        monteSimGenerator.setDividendCurve(factorSym, divYield);

        volInput = (inputs.underlyingInputs[i].strikeVolCurve ? inputs.underlyingInputs[i].strikeVolCurve.getVol({ //if strikeVolCurve exists, determine the volitility using expiration date and strike values
            expiry: expDate,
            side: 'mid',
            strike: spot  // * strike/100
        }) : inputs.underlyingInputs[i].volatility);
        
        //when using 'local' vol lookup, point to strike vol surface instead of fixed vol... and update tools, preferences, data, market data to use a parametric interpolation setting  
        //volInput = (inputs.underlyingInputs[0].strikeVolCurve ? inputs.underlyingInputs[0].strikeVolCurve : inputs.underlyingInputs[0].volatility);
        monteSimGenerator.setVolatilityCurve(factorSym, volInput);
        monteSimGenerator.setVolatilityLookupType(factorSym, "atm"); // we can also use the 'local volatility' lookup
        monteSimGenerator.setInitialValue(factorSym, spot);
    }

    monteSimGenerator.setPeriodEndDates([expDate]);
    monteSimGenerator.setAsofDate([asofDate]);
    monteSimGenerator.setAsofTime(inputs.asofTime);
    monteSimGenerator.setNumberOfSimulationsPerPeriod(numSimsPerPeriod);
    monteSimGenerator.setCorrelationFunction(getCorrelation);

    //Setting the Grid Size allows us to generate sub-paths to be used for calculating the Greeks  
    monteSimGenerator.setFactorGridSize(3);
    monteSimGenerator.setFactorGridSpacingPercent(1);

    var simulations = monteSimGenerator.getSimulations();

    // calculate payout
    let payout = inputs.payout;
    let simWeight = 1.0 / numSimsPerPeriod;
    let totalPayout = [];
    for (let i = 0; i < numFactors; ++i) {
        totalPayout.push([0,0,0]);
    }
    
    for (let sim = 0; sim < numSimsPerPeriod; ++sim) {
        let payoutOnSim = [];
        for (let i = 0; i < numFactors; ++i) {
            if (payout == "Best of Assets") {
                payoutOnSim.push([-1.0e6,-1.0e6,-1.0e6]);
            } else {
                payoutOnSim.push([1.0e6,1.0e6,1.0e6]);
            }
        }
        let periodIndex = 0; // only one period
        //format of simulated prices is: simulations[factorSym][sim][subPath][periodIndex];	
        
        for (let idx = 0; idx < numFactors; ++idx) {
            for (let factor = 0; factor < numFactors; ++factor) {
                let factorSym = inputs.underlyingInputs[factor].refSymbol;
                let endingPeriodValue = [];
                for (let i = 0; i < numFactors; ++i) {
                    endingPeriodValue.push([0,0,0]);
                }

                for (let subpath = 0; subpath < 3; ++subpath) {
                    // only apply subpath to one asset for calculating delta
                    endingPeriodValue[idx][subpath] = (factor == idx) ? simulations[factorSym][sim][subpath][0] / inputs.underlyingInputs[factor].level : simulations[factorSym][sim][1][0] / inputs.underlyingInputs[factor].level;
                    if (payout == "Best of Assets") {
                        if (endingPeriodValue[idx][subpath] > payoutOnSim[idx][subpath]) payoutOnSim[idx][subpath] = endingPeriodValue[idx][subpath];
                    } else {
                        if (endingPeriodValue[idx][subpath] < payoutOnSim[idx][subpath]) payoutOnSim[idx][subpath] = endingPeriodValue[idx][subpath];
                    }
                }
            }
            // add payoff of each simulation to total
            for (let subpath = 0; subpath < 3; ++subpath) {
                totalPayout[idx][subpath] += payoutOnSim[idx][subpath] * simWeight;
            }
        }
    }
    
    let discountFactorFromExpiry = inputs.domesticFinanceCurve.getDiscountFactor(asofDate, expDate);
    for (let idx = 0; idx < numFactors; ++idx) {
        for (let subpath = 0; subpath < 3; ++subpath) {
            totalPayout[idx][subpath] = (inputs.putCall == "Call") ? Math.max(totalPayout[idx][subpath] - strike/100, 0.0) : Math.max(strike/100 - totalPayout[idx][subpath], 0.0);
        }
    }
    
    let mydelta = [];
    let mygamma = [];

    for (let i = 0; i < numFactors; ++i) {
        let tmpUPrice = inputs.underlyingInputs[0]['underlyingPrice'];
        mydelta.push(discountFactorFromExpiry * (totalPayout[i][2] - totalPayout[i][0]) / (0.02 * tmpUPrice));
        mygamma.push(discountFactorFromExpiry * (totalPayout[i][0] + totalPayout[i][2] - 2 * totalPayout[i][1]) / (0.0001 * Math.pow(tmpUPrice, 2)));
    }
    
    outputGreeks.fair = totalPayout[0][1] * discountFactorFromExpiry;
    // focus on fair for now
    outputGreeks.delta = mydelta;
    outputGreeks.gamma = mygamma;
 //   outputGreeks.vega = 0;
 //   outputGreeks.theta = 0;
 //   outputGreeks.dv01 = 0;
    
    log("########################## RESULTS ##########################");
    log(outputGreeks);
    
    return outputGreeks;
}

function getCorrelation(days, syma, symb) {
    return analytics.customcolumn.corr(days, syma, symb);
}

function getCurrentTime() {
    return new Date().getTime();
}

function log(msg) {
    if (guiMode) {
        system.out.println(JSON.stringify(msg, null, '\t'));
    };
}

exports['RainbowOptionModel'] = RainbowOptionModel;