finance-lattice-model
Version:
Finance Lattice Model implementation
258 lines (239 loc) • 8.82 kB
JavaScript
const Constant = require("./Constants");
const _ = require("lodash");
const { v4: uuidv4 } = require("uuid");
const Decimal = require('decimal.js');
/** Class representing a Node in the Binary Tree*/
module.exports = {
Node: class Node {
/**
* Create a Node.
* @param {String} name - The unique name of the node.
* @param {Object} value - An object that contains values to represent the tree.
* @param {Object} children - Dictionary {UP: Node, DOWN: Node} of the children nodes.
*/
constructor(name, value, children, level = 0) {
this.id = uuidv4();
this.name = name;
if (value == null) {
const nameAsValue = Number(name);
if (Number.isNaN(nameAsValue)) {
throw new Error(`Node ${name} has no value`);
}
value = Decimal(nameAsValue);
}
this.value = value;
this.children = children || { UP: null, DOWN: null };
if (
this.hasChildren() &&
Decimal(this.children.UP.value).greaterThan(Decimal(this.children.DOWN.value))
) {
this.children = { UP: this.children.DOWN, DOWN: this.children.UP };
}
this.level = level;
}
/**
* Returns true or false depending if the Node has defined Children.
* @param {boolean} - True if the Node has defined both children.
*/
hasChildren() {
return (
!_.isEmpty(this.children) &&
this.children.UP != null &&
this.children.DOWN != null
);
}
/**
* Adds children pair to the node.
* @param {Number} u - The factor or multiplier by which the price will increase (up)
* @param {Number} d - The factor or multiplier by which the price will decrease (down)
* @param {Number} level - The level or period of the node.
* @param {Node} [sibling] - The adjacent node of the current node. Used to simplify the tree, since Node_i[UP] * DOWN = Node_i[DOWN] * UP
*/
addChildrenPair(u, d, level, sibling = null) {
let childUp, childDown;
const childUpValue = Decimal(this.value).times(u);
const childDownValue = Decimal(this.value).times(d);
if (sibling) {
const potentialUpChild = sibling
.getChildren()
.find((node) =>
Decimal(node.value).minus(childUpValue).lessThan(Decimal(1e-10))
);
const potentialDownChild = sibling
.getChildren()
.find((node) => Decimal(node.value).minus(childDownValue).lessThan(Decimal(1e-10)));
if (potentialUpChild) {
childUp = this.addChildren(Constant.Node.UP, potentialUpChild);
}
if (potentialDownChild) {
childDown = this.addChildren(Constant.Node.DOWN, potentialDownChild);
}
}
if (!childUp) {
childUp = this.addChildren(
Constant.Node.UP,
new Node(childUpValue, undefined, undefined, level)
);
}
if (!childDown) {
childDown = this.addChildren(
Constant.Node.DOWN,
new Node(childDownValue, undefined, undefined, level)
);
}
return this.children;
}
/**
* Returns the value of the node, with its profit.
* @returns {String} - Formatted string of the value and the profit of the node.
*/
getName() {
return `Value: $${this.value}, Profit: $${this.profit}`;
}
/**
* Returns an iterable array of the children nodes.
* @returns {Array} - Array where pos0 has ChildUp Node and pos1 has ChildDown Node.
*/
getChildren() {
const array = [];
if (this.children[Constant.Node.UP])
array.push(this.children[Constant.Node.UP]);
if (this.children[Constant.Node.DOWN])
array.push(this.children[Constant.Node.DOWN]);
return array;
}
/**
* Executes a method forAll nodes, starting will the children and ending with the current node.
* @param {Function} callback - The function to execute.
* @param {Array} args - The arguments or parameters of the function.
*/
forAll(callback, args) {
if (!this.hasChildren()) {
return callback.apply(this, args);
} else {
for (const child in this.children) {
if (Object.hasOwnProperty.call(this.children, child)) {
this.children[child].forAll(callback, args);
}
}
return callback.apply(this, args);
}
}
/**
* Returns true if both children have a defined profit.
* @returns {boolean} - True if both children have a defined profit.
*/
childrenHaveDefinedProfit() {
for (const child in this.children) {
if (Object.hasOwnProperty.call(this.children, child)) {
if (
this.children[child].profit == null ||
Number(this.children[child].profit) < -1
) {
return false;
}
}
}
return true;
}
/**
* Calculates the profit of a node.
* @param {String} option - The option to evaluate. @see Constants.Options;
* @param {Number} K - The strike price of the option.
* @param {Number} p - The probability for the price to go up.
* @param {Number} r - The risk free rate.
* @param {Number} dt - Time delta (1 / number of periods).
*/
setProfit(option, K, p, r, dt) {
if (!this.hasChildren()) {
switch (option) {
case Constant.Options.EUROPEAN.PUT:
case Constant.Options.AMERICAN.PUT:
this.profit = Decimal(K).greaterThan(Decimal(this.value))
? Decimal(K).minus(Decimal(this.value))
: Decimal(0);
break;
case Constant.Options.EUROPEAN.CALL:
case Constant.Options.AMERICAN.CALL:
this.profit = Decimal(this.value).greaterThan(Decimal(K))
? Decimal(this.value).minus(Decimal(K))
: Decimal(0);
break;
}
} else {
const expectedIfUp = Decimal(p).times(this.children[Constant.Node.UP].profit);
const expectedIfDown = (Decimal(1).minus(p)).times(
this.children[Constant.Node.DOWN].profit
);
let profitIfWaits = expectedIfUp.plus(expectedIfDown);
profitIfWaits = profitIfWaits.times(
Decimal(1)
.naturalExponential()
.toPower(Decimal(-r).times(Decimal(dt)))
);
switch (option) {
case Constant.Options.EUROPEAN.PUT:
case Constant.Options.EUROPEAN.CALL:
this.profit = profitIfWaits;
break;
case Constant.Options.AMERICAN.CALL:
this.profit = (Decimal(this.value).minus(Decimal(K))).greaterThan(profitIfWaits)
? Decimal(this.value).minus(Decimal(K))
: profitIfWaits;
break;
case Constant.Options.AMERICAN.PUT:
this.profit = (Decimal(K).minus(Decimal(this.value))).greaterThan(profitIfWaits)
? Decimal(K).minus(Decimal(this.value))
: profitIfWaits;
break;
}
}
}
/**
* Adds a children in a defined position (UP or DOWN)
* @returns {Node} - The node added to the children.
*/
addChildren(position, newNode) {
this.children[position] = newNode;
return newNode;
}
/**
* Deletes a children in a defined position (UP or DOWN)
* @returns {Node} - The node deleted of the children.
*/
removeChildren(position) {
if (!this.hasChildren()) {
return null;
} else {
const node = _.cloneDeep(this.children[position]);
delete this.children[position];
return node;
}
}
/**
* Imports a JSON representation of a tree.
* @param {String} JSONNode - The JSON representation of the tree.
*/
static import(JSONNode) {
if (!JSONNode.children || _.isEmpty(JSONNode.children)) {
const response = new Node(JSONNode.name, JSONNode.value, null);
return response;
} else {
const response = new Node(JSONNode.name, JSONNode.value, null);
for (const child in JSONNode.children) {
if (Object.hasOwnProperty.call(JSONNode.children, child)) {
const element = JSONNode.children[child];
response.addChildren(child, Node.import(element));
}
}
return response;
}
}
/**
* Prints the current node in console using console.table()
*/
print() {
console.table(this);
}
},
};