Trees are a great way to visualize different states of a process when measuring uncertainty and making predictions. While trees are harder to implement in SQL due to the relational nature of the language, they are powerful tools in finance, for instance, to value derivatives.
In this blog, we discuss how to set up a discrete binomial tree using the binomial options pricing model to value derivatives in Postgres SQL and apply this in an example of pricing a 3-period call option.
Setting Up A Binomial Tree
To create a tree in Postgres, we utilize the generate_series() function. Since the state of a process, which we denote here as a node, can be identified by the depth and width position of the node, we first generate a 4×7 lattice. We then truncate nodes that do not belong to the binomial tree.
with
a as (
select *
from
generate_series(0, 3, 1)
)
, b as (
select *
from
generate_series(-3, 3, 1)
)
, tree as (
select a.generate_series as t
, b.generate_series as branch
from a, b
where
case when mod(a.generate_series, 2) = 1
then @ b.generate_series <= a.generate_series
and mod(@ b.generate_series, 2) = 1
else @ b.generate_series <= a.generate_series
and mod(@ b.generate_series, 2) = 0
end
)

In the above CTEs, the tree table skims off unnecessary nodes that are outside of the symmetrical binomial tree through the where clause.
Binomial Options Pricing
An option is a derivative whose value is derived by the price of the underlying stock. A call option is a contract that gives the option holder the right to purchase the stock at an agreed strike price.
Since the value of the option can change depending on the state of the stock, its value is perfect for modeling out via the branches in a tree.
We will now walk through how to value a 3-period European call option whose underlying stock price is $83, has a strike price of $85, and in each period can move up by a factor of 1.1 or down by a factor of 0.9.
With the 3-period tree, we can build out the option pricing backwards iteratively. First, we need to find the option payout max[(S_3-K,0] at each of the final nodes (t = 3).
, final as (
select *
, case when t = 3
and 83 * 1.1^(t-(t-branch)/2)
*0.9^((t-branch)/2) > 85
then 83 * 1.1^(t-(t-branch)/2)
*0.9^((t-branch)/2) - 85
when t = 3
then 0
else null
end as final_val
from tree
where t = 3
)

To work backwards iteratively for valuing options, we use the following formula for each step backward:

In order to calculate the risk-neutral value at t = 0, we have to take into account not only the expected value at each node, but also the probability of traversing through the node. Thus, to calculate the contribution of the final payout to the expected value of the option at each node, we use the following formula:

Encoding all of this into SQL, we get:
select
lt.t
, lt.branch
, rt.t as final_t
, rt.branch as final_branch
, final_val
, (1/exp(0.05)^(rt.t-lt.t))*(((rt.t-lt.t)!)/((((rt.t-lt.t)
-(((rt.t-lt.t)-(rt.branch-lt.branch))/2))!)*((((rt.t-lt.t)
-(rt.branch-lt.branch))/2)!)))*final_val
*((exp(0.05)-0.9)/(1.1-0.9))^((rt.t-lt.t)-((rt.t-lt.t)
-(rt.branch-lt.branch))/2)*(1-(exp(0.05)-0.9)/(1.1-0.9))
^(((rt.t-lt.t)-(rt.branch-lt.branch))/2) as disc_val
from
tree lt, tree rt
join final on rt.branch = final.branch
and rt.t = final.t
where
lt.t <= rt.t
and @(rt.branch - lt.branch) <= @(rt.t - lt.t)
and rt.t = 3
order by branch desc

One handy tool that Sisense for Cloud Data Teams offers is the Cohort Grid, which aggregates these expected values by node. Here’s what it looks like in tree form:

Now you can price your options through SQL!