/// <summary> /// Price an option under the specified trinomial tree gird. /// </summary> /// <param name="function"> the option </param> /// <param name="data"> the trinomial tree data </param> /// <returns> the option price </returns> public virtual double optionPrice(OptionFunction function, RecombiningTrinomialTreeData data) { int nSteps = data.NumberOfSteps; ArgChecker.isTrue(nSteps == function.NumberOfSteps, "mismatch in number of steps"); DoubleArray values = function.getPayoffAtExpiryTrinomial(data.getStateValueAtLayer(nSteps)); for (int i = nSteps - 1; i > -1; --i) { values = function.getNextOptionValues(data.getDiscountFactorAtLayer(i), data.getProbabilityAtLayer(i), data.getStateValueAtLayer(i), values, i); } return(values.get(0)); }
/// <summary> /// Test consistency between price methods, and Greek via finite difference. /// </summary> public virtual void test_trinomialTree() { int nSteps = 135; double dt = TIME / nSteps; LatticeSpecification lattice = new CoxRossRubinsteinLatticeSpecification(); double fdEps = 1.0e-4; foreach (bool isCall in new bool[] { true, false }) { foreach (double strike in STRIKES) { foreach (double interest in INTERESTS) { foreach (double vol in VOLS) { foreach (double dividend in DIVIDENDS) { OptionFunction function = EuropeanVanillaOptionFunction.of(strike, TIME, PutCall.ofPut(!isCall), nSteps); double[] @params = lattice.getParametersTrinomial(vol, interest - dividend, dt).toArray(); DoubleArray time = DoubleArray.of(nSteps + 1, i => dt * i); DoubleArray df = DoubleArray.of(nSteps, i => Math.Exp(-interest * dt)); double[][] stateValue = new double[nSteps + 1][]; stateValue[0] = new double[] { SPOT }; IList <DoubleMatrix> prob = new List <DoubleMatrix>(); double[] probs = new double[] { @params[5], @params[4], @params[3] }; for (int i = 0; i < nSteps; ++i) { int index = i; stateValue[i + 1] = DoubleArray.of(2 * i + 3, j => SPOT * Math.Pow(@params[2], index + 1 - j) * Math.Pow(@params[1], j)).toArray(); double[][] probMatrix = new double[2 * i + 1][]; Arrays.fill(probMatrix, probs); prob.Add(DoubleMatrix.ofUnsafe(probMatrix)); } RecombiningTrinomialTreeData treeData = RecombiningTrinomialTreeData.of(DoubleMatrix.ofUnsafe(stateValue), prob, df, time); double priceData = TRINOMIAL_TREE.optionPrice(function, treeData); double priceParams = TRINOMIAL_TREE.optionPrice(function, lattice, SPOT, vol, interest, dividend); assertEquals(priceData, priceParams); ValueDerivatives priceDeriv = TRINOMIAL_TREE.optionPriceAdjoint(function, treeData); assertEquals(priceDeriv.Value, priceData); double priceUp = TRINOMIAL_TREE.optionPrice(function, lattice, SPOT + fdEps, vol, interest, dividend); double priceDw = TRINOMIAL_TREE.optionPrice(function, lattice, SPOT - fdEps, vol, interest, dividend); double fdDelta = 0.5 * (priceUp - priceDw) / fdEps; assertEquals(priceDeriv.getDerivative(0), fdDelta, 3.0e-2); } } } } } }
/// <summary> /// Compute option price and delta under the specified trinomial tree gird. /// <para> /// The delta is the first derivative of the price with respect to spot, and approximated by the data embedded in /// the trinomial tree. /// /// </para> /// </summary> /// <param name="function"> the option </param> /// <param name="data"> the trinomial tree data </param> /// <returns> the option price and spot delta </returns> public virtual ValueDerivatives optionPriceAdjoint(OptionFunction function, RecombiningTrinomialTreeData data) { int nSteps = data.NumberOfSteps; ArgChecker.isTrue(nSteps == function.NumberOfSteps, "mismatch in number of steps"); DoubleArray values = function.getPayoffAtExpiryTrinomial(data.getStateValueAtLayer(nSteps)); double delta = 0d; for (int i = nSteps - 1; i > -1; --i) { values = function.getNextOptionValues(data.getDiscountFactorAtLayer(i), data.getProbabilityAtLayer(i), data.getStateValueAtLayer(i), values, i); if (i == 1) { DoubleArray stateValue = data.getStateValueAtLayer(1); double d1 = (values.get(2) - values.get(1)) / (stateValue.get(2) - stateValue.get(1)); double d2 = (values.get(1) - values.get(0)) / (stateValue.get(1) - stateValue.get(0)); delta = 0.5 * (d1 + d2); } } return(ValueDerivatives.of(values.get(0), DoubleArray.of(delta))); }
//----------------------------------------------------------------------- private Pair <ImmutableList <double[]>, RecombiningTrinomialTreeData> calibrate(System.Func <DoublesPair, double> impliedVolatilitySurface, double spot, System.Func <double, double> interestRate, System.Func <double, double> dividendRate) { double[][] stateValue = new double[nSteps + 1][]; double[] df = new double[nSteps]; double[] timePrim = new double[nSteps + 1]; IList <DoubleMatrix> probability = new List <DoubleMatrix>(nSteps); int nTotal = (nSteps - 1) * (nSteps - 1) + 1; double[] timeRes = new double[nTotal]; double[] spotRes = new double[nTotal]; double[] volRes = new double[nTotal]; // uniform grid based on TrigeorgisLatticeSpecification double volatility = impliedVolatilitySurface(DoublesPair.of(maxTime, spot)); double dt = maxTime / nSteps; double dx = volatility * Math.Sqrt(3d * dt); double upFactor = Math.Exp(dx); double downFactor = Math.Exp(-dx); double[] adSec = new double[2 * nSteps + 1]; double[] assetPrice = new double[2 * nSteps + 1]; for (int i = nSteps; i > -1; --i) { timePrim[i] = dt * i; if (i == 0) { resolveFirstLayer(interestRate, dividendRate, nTotal, dt, spot, adSec, assetPrice, timeRes, spotRes, volRes, df, stateValue, probability); } else { double zeroRate = interestRate(timePrim[i]); double zeroDividendRate = dividendRate(timePrim[i]); double zeroCostRate = zeroRate - zeroDividendRate; int nNodes = 2 * i + 1; double[] assetPriceLocal = new double[nNodes]; double[] callOptionPrice = new double[nNodes]; double[] putOptionPrice = new double[nNodes]; int position = i - 1; double assetTmp = spot * Math.Pow(upFactor, i); // call options for upper half nodes for (int j = nNodes - 1; j > position - 1; --j) { assetPriceLocal[j] = assetTmp; double impliedVol = impliedVolatilitySurface(DoublesPair.of(timePrim[i], assetPriceLocal[j])); callOptionPrice[j] = BlackScholesFormulaRepository.price(spot, assetPriceLocal[j], timePrim[i], impliedVol, zeroRate, zeroCostRate, true); assetTmp *= downFactor; } // put options for lower half nodes assetTmp = spot * Math.Pow(downFactor, i); for (int j = 0; j < position + 2; ++j) { assetPriceLocal[j] = assetTmp; double impliedVol = impliedVolatilitySurface(DoublesPair.of(timePrim[i], assetPriceLocal[j])); putOptionPrice[j] = BlackScholesFormulaRepository.price(spot, assetPriceLocal[j], timePrim[i], impliedVol, zeroRate, zeroCostRate, false); assetTmp *= upFactor; } resolveLayer(interestRate, dividendRate, i, nTotal, position, dt, zeroRate, zeroDividendRate, callOptionPrice, putOptionPrice, adSec, assetPrice, assetPriceLocal, timeRes, spotRes, volRes, df, stateValue, probability); } } ImmutableList <double[]> localVolData = ImmutableList.of(timeRes, spotRes, volRes); RecombiningTrinomialTreeData treeData = RecombiningTrinomialTreeData.of(DoubleMatrix.ofUnsafe(stateValue), probability, DoubleArray.ofUnsafe(df), DoubleArray.ofUnsafe(timePrim)); return(Pair.of(localVolData, treeData)); }