FE 459
Spring 2026

Assignment 6

Discussion on Wednesday 2/25/26
Workshop in class on Monday 3/2/26, Wednedsay 3/4/26
Submission due by 9:00 p.m. on Friday 3/6/26.

Preliminaries

In your work on this assignment, make sure to abide by the collaboration policies of the course.

For each problem in this problem set, we will be writing or evaluating some Python code. You are encouraged to use the Spyder IDE which will be discussed/presented in class, but you are welcome to use another IDE if you choose.

If you have questions while working on this assignment, please post them on Piazza! This is the best way to get a quick response from your classmates and the course staff.

Programming Guidelines

  • Refer to the class Coding Standards for important style guidelines. The grader will be awarding/deducting points for writing code that comforms to these standards.

  • Every program file must begin with a descriptive header comment that includes your name, username/BU email, and a brief description of the work contained in the file.

  • Every function must include a descriptive docstring that explains what the function does and identifies/defines each of the parameters to the function.

  • Your functions must have the exact names specified below, or we won’t be able to test them. Note in particular that the case of the letters matters (all of them should be lowercase), and that some of the names include an underscore character (_).

  • If a function takes more than one input, you must keep the inputs in the order that we have specified.

  • You should not use any Python features that we have not discussed in class or read about in the textbook.

  • Your functions do not need to handle bad inputs – inputs with a type or value that doesn’t correspond to the description of the inputs provided in the problem.

  • You must test each function after you write it.

Warnings: Individual Work, Generative AI, and Academic Conduct!!

  • This is an individual assignment. You may discuss the problem statement/requirements, Python syntax, test cases, and error messages with your classmates. However, each student must write their own code without copying or referring to other student’s work.

  • It is strictly forbidden to use any code that you find from online websites including but not limited to as CourseHero, Chegg, or any other sites that publish homework solutions.

  • It is strictly forbidden to use any generative AI (e.g., ChatGPT, Claude, Gemini, CoPilot or any similar tools**) to write solutions for any assignment.

Students who submit work that is not authentically their own individual work will earn a grade of 0 on this assignment and a reprimand from the office of the Dean.

If you have questions while working on this assignment, please post them on Piazza! This is the best way to get a quick response from your classmates and the course staff.

Objective and Overview

Objective: Learn about the bionomial trees and the binomial option pricing model as a discrete-time approach to simulating stock price movements and valuing European and American style options. In addition, you will gain practice with writing user-defined classes, inheritance, loops, and 2-d lists.

Overview: In this assignment, you will develop some user defined classes and methods to implement binomial trees and to compute option prices using the binomial option pricing model, which provides a good approximation of option prices.

Background: Options are derivative contracts, the eventual value of which depends on some underlying asset (i.e., a stock) at a maturity (expiration) time in the future.

We assume that the price of the underlying asset follows a binomial distribution of possible values. At each period, the underlying asset can either move up or down, according to some risk-neutral probability (explained below). Over subsequence periods, the underlying asset can again move up or down from the previous period.

At the limit, the binomial distribution will approximate the normal distribution, so this model gives us a discrete approximation that will give us option prices that are similar to what we would achieve with a continuous-time approach (i.e., Black-Scholes).

Task 1: Creating Binomial Stock Price Trees

30 points; individual-only

Do all of your work for Task 1 of this assignment in a file called a6task1.py. As with previous assignments, you must use Python lists and loops to develop your solution. You may not use built-in packages such as numpy or pandas.

To begin, assume that a stock is currently priced at $90, and we want to consider price movements over 6 months (0.5 years), which we further break down into 2 discrete time periods (i.e., of 0.25 years each). Here is a binomial tree illustrating the potential stock price paths over 2 periods, from an initial price of $90:

BinomialTree (s=90, stdev=0.3, rf=0.06, div=0, t=0.5 years, nper=2)
 90.00    106.15    125.19
           78.63     92.74
                     68.70

The binomial price tree will be encapsulated within a class BinomialTree, which will develop a recombinant binomial tree suitable for any underlying asset, e.g., a stock. The class will have the following data attributes:

In addition, the BinomialTree will calculate and store several additional data attributes, representing parameters used in the calculation of the binomial price tree.

  1. Write the constructor __init__(self, s, stdev, rf, div, years, nper), which will initialize a BinomialTree object.

    In addition, the constructor of the BinomialTree will call a helper method to build the binomial tree as a 2-dimensional list (upper right triangle of a matrix). See build_tree below.

    Here is an example of creating a BinomialTree object, and printing it out using the __repr__ method (See below):

    >>> bt = BinomialTree (90, 0.3, 0.06, 0, 0.5, 2)
    >>> bt # calls the __repr__ method
    BinomialTree (s=90, stdev=0.3, rf=0.06, div=0, t=0.5 years, nper=2)
    
  2. Write the __repr__ method to obtain a beautifully-formatted string representation of the BinomialTree object.

    To begin, the method should print out the data attributes of the object, i.e.:

    >>> bt = BinomialTree (90, 0.3, 0.06, 0, 0.5, 2)
    >>> bt
    BinomialTree (s=90, stdev=0.3, rf=0.06, div=0, t=0.5 years, nper=2)
    
  3. Write the helper method build_tree(self), which takes no parameters.

    This method should build a binomial tree (as a 2-d list), which will simulate the price movements of the s asset. Store the 2-d list as a data attribute of this class called tree.

    To begin, build_tree should calculate and store the parameters h, u and d, which will be needed to build out the tree. h is the length of each time period, which can be computed as h = t/n.

    For each period, we assume the stock has a chance of moving up or down, and we calculate the prices in the states up and down using the factors u and d. The price in the up-state is u times the previous price, and the price in the down-state us d times the previous price.

    A First Example:

    >>> # here we consider a one-period model, which has only two possible stock price outcomes after 1 year:
    >>> bt = BinomialTree (90, 0.3, 0.06, 0, 1, 1)
    >>> bt.tree # access the 2-d list
    [[90, 128.99964731043062], [0.0, 70.79650749598981]]
    

    Notice that bt.tree is a 2-d list of values. We can visualize bt.tree better using the print_matrix function that you wrote in Assignment 4, as it helps line up the rows correctly:

    >>> from a4task1 import print_matrix
    >>> bt = BinomialTree (90, 0.3, 0.06, 0, 1, 1)
    >>> print_matrix(bt.tree)
    [[90, 129] 
     [0.0, 70.80]]
    

    Notice also that this is a 2-d list, with values in the upper-right triangle only. The lower-left triangle should have 0s.

    In the next part (number 4, below), you will enhance the __repr__ method to produce a beaut-fully-formatted binomial tree, but for now using print_matrix will suffice.

    A Second Example:

    >>> from a4task1 import print_matrix
    >>> # here we consider five possible stock price outcomes over the next 1 year:
    >>> bt = BinomialTree (90, 0.3, 0.06, 0, 1, 5)
    >>> print_matrix(bt.tree)
    [[90.00, 104.16, 120.56, 139.53, 161.49, 186.91]
     [0.00, 79.65, 92.19, 106.69, 123.49, 142.92]
     [0.00, 0.00, 70.49, 81.59, 94.43, 109.29]
     [0.00, 0.00, 0.00, 62.38, 72.20, 83.57]
     [0.00, 0.00, 0.00, 0.00, 55.21, 63.90]
     [0.00, 0.00, 0.00, 0.00, 0.00, 48.86]]
    

    Notice again that this is a 2-d list, with values in the upper-right triangle only. The lower-left triangle should have 0s.

    Notes:

    • Re-use your code! Your build_tree method should use the binomial parameters that are stored as data attributes of the BinomialTree object.

    • Call this method as the last line of the constructor, so that upon creating the BinomialTree object the tree is initialized and ready to use.

    • The initial underlying price S will be the root (left-most) node of the price tree. As such, you will need to create a 2-dimensional list of size nper+1 by n+1 (See example above) to be able to create nper discrete periods after the initial period.

  4. Return to your __repr__ method, to create a beautifully-formatted representation of your BinomialTree as an upper-right triangle (with bottom-left elements not shown).

    Example:

    >>> # 1 year, 2 periods
    >>> bt = BinomialTree (90, 0.3, 0.06, 0, 1, 2)
    >>> bt
    BinomialTree (s=90, stdev=0.3, rf=0.06, div=0, t=1 years, nper=2)
         90.00    114.66    146.07
                   75.01     95.57
                             62.52
    

    Notes:

    • You should always assume that the 2-dimensional list is a square, but that you will only print out the upper-right triangle of the matrix.

    • The printout must have each row on its own line, and use tabs or spaces as needed to create a tree-like illustration.

    • Print each number with 2 digits of precision after the decimal point (but don’t change the underlying data in the 2d list, which must continue to be numeric).

    Additional Examples:

    >>> # 1 year, 5 periods
    >>> bt = BinomialTree (90, 0.3, 0.06, 0, 1, 5)
    >>> bt
    BinomialTree (s=90, stdev=0.3, rf=0.06, div=0, t=1 years, nper=5)
         90.00    104.16    120.56    139.53    161.49    186.91
                   79.65     92.19    106.69    123.49    142.92
                             70.49     81.59     94.43    109.29
                                       62.38     72.20     83.57
                                                 55.21     63.90
                                                           48.86
    
    >>> # 1 year, 10 periods
    >>> bt = BinomialTree (90, 0.3, 0.06, 0, 1, 10)
    >>> bt
    BinomialTree (s=90, stdev=0.3, rf=0.06, div=0, t=1 years, nper=10)
         90.00     99.55    110.12    121.80    134.73    149.03    164.85    182.34    201.69    223.10    246.78
                   82.35     91.09    100.75    111.45    123.27    136.36    150.83    166.84    184.54    204.13
                             75.34     83.34     92.19    101.97    112.79    124.76    138.00    152.65    168.85
                                       68.94     76.25     84.35     93.30    103.20    114.15    126.27    139.67
                                                 63.08     69.77     77.17     85.37     94.43    104.45    115.53
                                                           57.71     63.84     70.61     78.11     86.40     95.57
                                                                     52.80     58.41     64.61     71.46     79.05
                                                                               48.31     53.44     59.11     65.39
                                                                                         44.21     48.90     54.09
                                                                                                   40.45     44.74
                                                                                                             37.01
    
  5. Create mutator methods (e.g., set_s) to be able to change each of the data attributes of the object. Changing any data attribute will change the prices in the tree, so it is important that these methods also call the build_tree method.

    Here are some examples:

    >>> bt = BinomialTree (90, 0.3, 0.06, 0, 1, 4)
    >>> bt
    BinomialTree (s=90, stdev=0.3, rf=0.06, div=0, t=1 years, nper=4)
         90.00    106.15    125.19    147.64    174.13
                   78.63     92.74    109.38    129.00
                             68.70     81.03     95.57
                                       60.03     70.80
                                                 52.45
    

    Changing the s results in shifting the starting node, and also the proportional movements from this starting point:

    >>> bt.set_s(100)
    >>> bt
    BinomialTree (s=100, stdev=0.3, rf=0.06, div=0, t=1 years, nper=4)
        100.00    117.94    139.10    164.05    193.48
                   87.37    103.05    121.53    143.33
                             76.34     90.03    106.18
                                       66.70     78.66
                                                 58.27
    

    Increasing the standard deviation results in widening the distribution of prices (both upper and lower) branches:

    >>> bt.set_stdev(0.4)
    >>> bt
    BinomialTree (s=100, stdev=0.4, rf=0.06, div=0, t=1 years, nper=4)
        100.00    123.99    153.73    190.60    236.32
                   83.11    103.05    127.76    158.41
                             69.07     85.64    106.18
                                       57.41     71.18
                                                 47.71
    

    Increasing the number of periods results in both more nodes, and also smaller price movements in each node:

    >>> bt.set_nper(5)
    >>> bt
    BinomialTree (s=100, stdev=0.4, rf=0.06, div=0, t=1 years, nper=5)
        100.00    121.03    146.49    177.30    214.59    259.72
                   84.63    102.43    123.97    150.05    181.60
                             71.62     86.69    104.92    126.98
                                       60.61     73.36     88.79
                                                 51.30     62.09
                                                           43.41
    

    Decreasing the risk-free rate decreases the amount of the up and down movements between each node:

    >>> bt.set_rf(0.03)
    >>> bt 
    BinomialTree (s=100, stdev=0.4, rf=0.03, div=0, t=1 years, nper=5)
    100.00    120.31    144.74    174.13    209.50    252.04
               84.12    101.21    121.76    146.49    176.24
                         70.77     85.14    102.43    123.23
                                   59.53     71.62     86.17
                                             50.08     60.25
                                                       42.13
    

Task 2: Binomial Options Pricing

50 points; individual-only

Overview and Methodology

The value of an option depends on the price of the underlying security (e.g., stock). The bionomial option pricing model uses the value of an option at maturity (given the price of the underlying), and then works backwards to find the value of the option today.

Consider this one-period example for a European call option, which gives the right (but not the obligation) to purchase a stock in one year:

>>> # consider the posisble stock price in one year:
>>> bt = BinomialTree(25, 0.25, 0.05, 0.03, 1, 1)
>>> print(bt)
BinomialTree (s=25, stdev=0.25, rf=0.05, div=0.03, t=1 years, nper=1)
     25.00     32.75
               19.86 
>>> # the payoff of the option at maturity (time 1) depends on the 
    # underlying price and the strike price:
>>> call = BinomialEuroCallOption(25, 22, 0.25, 0.05, 0.03, 1, 1)
European Call Option Tree (s=25, x=22, stdev=0.25, rf=0.05, div=0.03, t=1 years, nper=1)
      4.48     10.75
                0.00

The call option’s value depends on the stock price at maturity and the execute price. In the above example, we assume that after 1 year, the stock price can rise to $32.75 or fall to $19.86.

The value of the call option is C = MAX(S - X, 0).

The payoff value of the stock option in the “up” state is $32.75 - $22 = $10.75. The payoff value of the option in the “down” state would be $19.86 - $22 = $-2.14 ... therefore the option holder will not execute the option, so it’s value is $0.

Given these two possible states, we find the value of the option at time 0 (today) by finding the “expected value” at maturity (using the risk-neutral probability of the “up” and “down” states), and discount this expected value to the present.

The same procedure will work for any number of binomial periods.

Call option value

At maturity (in the right-most column), the value of a call option at maturity is simply its payoff, i.e., the amount by which the stock price in the corresponding node exceeds the strike price: C = MAX(S - X, 0).

Once the right-most nodes have been calculated, we can work backwords for all other nodes. In general, the value of the call option in a given node is calculated by:

where Ci,j is the option’s value for node in position i,j of the tree, and depends on the value in the nodes Ci, j+1 and Ci+1, j+1.

Continue to work right-to-left until you reach the root of the tree.

Put option value

At maturity (in the right-most column), the value of a put option at maturity is simply its payoff, i.e., the amount by which the strike price in the corresponding node exceeds the stock price: P = MAX(X - S, 0).

Once the right-most nodes have been calculated, we can work backwords for all other nodes. In general, the value of the put option in a given node is calculated by:

where Pi,j is the option’s value for node in position i,j of the tree, and depends on the value in the nodes Pi, j+1 and Pi+1, j+1.

Continue to work right-to-left until you reach the root of the tree.


Do the work for this part in a file called a6task2.py. At the top of your file, you will need to import your work from Task 1, i.e.,

    from a6task1 import *
  1. Write the class definition for the BinomialOption class, which is a subclass of the BinomialTree from Task 1. This object will be an abstract class, i.e., we wil not actually create objects of this type. Rather, we will use it as a building block for several specific sub-classes, which will share most details but implement specific pricing algorithms.

    The constructor (__init__ method) must take parameters for:

    • s, the price of the underlying (e.g. stock) now
    • x, the strike price of the option
    • stdev, the annualized standard deviation of the returns on s
    • rf, the annualized risk-free rate
    • div, the annualized divident yield
    • t, the time to maturity (in years)
    • nper, number of periods to use in the bionomial tree

    The constructor (__init__ method) must begin by calling the superclass constructor, i.e., to initialize the part of the BinomialOption which is a BinomialTree object. To do so, use the syntax:

    super().__init__( < parameters> )
    

    to supply the constructor parameters required for class BinomialTree.

    Next, the constructor must store the data attribute x, which is specific to the BinomialOption class (i.e., not part of the superclass).

    Finally, the constructor will call a helper method build_option_tree. This method will be implemented on each of various subclasses, corresponding to specific type of options. It is not possible to call this method on the generic BinomialOption, but polymorphism will handle the detail of calling the correct subclass version of the method. Add the method call to the constructor now, but you will test it when you create objects of the subclass (See below).

  2. Write the class definition for the BinomialEuroCallOption class, which is a subclass of the BinomialOption superclass.

    The BinomialEuroCallOption will provide a concrete implementation of the option pricing algorithm for a European-style call option, which is exercisable only at maturity.

    You do not need to write a constructor for this class! The constructor for the superclass BinomialOption does exactly what we need.

    Write a __repr__ method to produce a string representation of this object. To begin, you may print out the data attributes:

    >>> call = BinomialEuroCallOption(25, 22, 0.25, 0.05, 0.03, 1, 1)
    >>> call
    European Call Option Tree (s=25, x=22, stdev=0.25, rf=0.05, div=0.03, t=1 years, nper=1)
    

    Next, write the method build_option_tree method, which will calculate the option value at each node in the tree, and save it as a data attribute called option_tree.

    • Create an empty 2-dimensional list of the appropriate dimensions to hold the option values for each node in the tree.

    • Use the superclass’s binomial tree, e.g., self.tree, to find the value of the underlying in each node. Begin with the right-most column, and compute the value of the European Call Option in each node, i.e., MAX(S - X, 0).

    • Work right-to-left to find the option value in each preceding node. The value in each preceding node is the weighted-average of the up and down states reachable from that node.

    Update your __repr__ method to display the option_tree with formatting, e.g.,

    >>> call = BinomialEuroCallOption(25, 22, 0.25, 0.05, 0.03, 1, 1)
    >>> call
    European Call Option Tree (s=25, x=22, stdev=0.25, rf=0.05, div=0.03, t=1 years, nper=1)
      4.48     10.75
                0.00
    

    Here is another example with which to test your work – an “in-the-money” call option on a dividen-paying stock, with 1 years to maturity and 2 periods in the binomial option pricing process.

    >>> call = BinomialEuroCallOption(25, 22, 0.25, 0.05, 0.03, 1, 2)
    >>> call
    European Call Option Tree (s=25, x=22, stdev=0.25, rf=0.05, div=0.03, t=1 years, nper=2)
          4.49      8.23     14.32
                    1.56      3.51
                              0.00
    

    Here is a second example, a call option on a dividen-paying stock, with 0.25 years to maturity, calculated with 5 periods in the bionomial process.

    >>> call = BinomialEuroCallOption(25, 22, 0.25, 0.05, 0.03, 0.25, 5)
    >>> call
    European Call Option Tree (s=25, x=22, stdev=0.25, rf=0.05, div=0.03, t=0.25 years, nper=5)
          3.32      4.58      6.05      7.67      9.40     11.23
                    2.15      3.20      4.55      6.08      7.71
                              1.16      1.95      3.12      4.57
                                        0.41      0.85      1.76
                                                  0.00      0.00
                                                            0.00
    

    Here is another example: For example, here is the European call option value tree for these parameters:

    >>> # s = 90, x = 100, sigma = 0.3, rf = 0.06, div = 0.02, t = 0.25, n = 10:
    >>> call = BinomialEuroCallOption(90, 100, 0.3, 0.06, 0.02, 0.25, 10)
    >>> call                                                                           
    European Call Option Tree (s=90, x=100, stdev=0.3, rf=0.06, div=0.02, t=0.25 years, nper=10)
      2.11      3.32      5.10      7.63     11.06     15.45     20.71     26.58     32.76     39.25     46.08
                0.96      1.63      2.71      4.39      6.90     10.47     15.17     20.77     26.66     32.86
                          0.33      0.61      1.11      2.00      3.52      6.02      9.88     15.21     20.83
                                    0.06      0.13      0.27      0.56      1.15      2.35      4.82      9.90
                                              0.00      0.00      0.00      0.00      0.00      0.00      0.00
                                                        0.00      0.00      0.00      0.00      0.00      0.00
                                                                  0.00      0.00      0.00      0.00      0.00
                                                                            0.00      0.00      0.00      0.00
                                                                                      0.00      0.00      0.00
                                                                                                0.00      0.00
                                                                                                          0.00
    
  3. Revisit the BinomialOption to add mutator methods for all data attributes. Follow the naming convention set_<attribute_name>, e.g., set_x. These methods must call the superclass version of the mutator method (So that the superclass will rebuild its price tree), and then must recalculate the option’s value by calling the build_option_tree method again.

    Here are several examples of using the mutator methods (by calling them on the subclass).

    >>> call = BinomialEuroCallOption(25, 22, 0.25, 0.05, 0.03, 0.25, 5)
    >>> call
    European Call Option Tree (s=25, x=22, stdev=0.25, rf=0.05, div=0.03, t=0.25 years, nper=5)
          3.32      4.58      6.05      7.67      9.40     11.23
                    2.15      3.20      4.55      6.08      7.71
                              1.16      1.95      3.12      4.57
                                        0.41      0.85      1.76
                                                  0.00      0.00
                                                            0.00
    
    >>> call.set_x(20) # lower the strike price; call becomes more valuable
    >>> call
    European Call Option Tree (s=25, x=20, stdev=0.25, rf=0.05, div=0.03, t=0.25 years, nper=5)
          5.10      6.50      8.04      9.66     11.39     13.23
                    3.79      5.09      6.54      8.08      9.71
                              2.58      3.74      5.11      6.57
                                        1.50      2.46      3.76
                                                  0.60      1.25
                                                            0.00
    
    >>> call.set_years(1) # lengthen the maturity to 1 year; call becomes more valuable
    >>> call
    European Call Option Tree (s=25, x=20, stdev=0.25, rf=0.05, div=0.03, t=1 years, nper=5)
          5.76      8.32     11.54     15.36     19.69     24.61
                    3.57      5.59      8.35     11.78     15.67
                              1.83      3.23      5.45      8.52
                                        0.61      1.31      2.81
                                                  0.00      0.00
                                                            0.00
    
    >>> call.set_rf(0.10) # set risk free rate higher; call becomes more valuable
    >>> call
    European Call Option Tree (s=25, x=20, stdev=0.25, rf=0.1, div=0.03, t=1 years, nper=5)
          6.47      9.28     12.74     16.81     21.50     26.89
                    4.22      6.53      9.59     13.26     17.50
                              2.31      4.04      6.68      9.98
                                        0.85      1.84      3.98
                                                  0.00      0.00
                                                            0.00
    
    >>> call.set_stdev(0.4) # increase standard deviation; call becomes more valuable
    >>> call
    European Call Option Tree (s=25, x=20, stdev=0.4, rf=0.1, div=0.03, t=1 years, nper=5)
          7.34     11.49     17.28     24.84     34.15     45.58
                    4.14      7.07     11.59     17.98     25.86
                              1.85      3.56      6.68     12.06
                                        0.48      1.08      2.42
                                                  0.00      0.00
                                                            0.00
    

    Add a value method, that will return the value of the option at time 0.

    >>> call = BinomialEuroCallOption(25, 22, 0.25, 0.05, 0.03, 0.25, 5)
    >>> call.value() # returns the value as a floating-point number
    3.3190742916994407
    
  4. Write the class definition for the BinomialEuroPutOption class, which is a subclass of the BinomialOption superclass.

    The BinomialEuroPutOption will provide a concrete implementation of the option pricing algorithm for a European-style put option, which is exercisable only at maturity.

    You do not need to write a constructor for this class! The constructor for the superclass BinomialOption does exactly what we need.

    Write a __repr__ method to produce a string representation of this object. To begin, you may print out the data attributes:

    >>> put = BinomialEuroPutOption(25, 22, 0.25, 0.05, 0.03, 1, 1)
    >>> put
    European Put Option Tree (s=25, x=22, stdev=0.25, rf=0.05, div=0.03, t=1 years, nper=1)
    

    Next, write the method build_option_tree method, which will calculate the option value at each node in the tree, and save it as a data attribute called option_tree.

    • Create an empty 2-dimensional list of the appropriate dimensions to hold the option values for each node in the tree.

    • Use the superclass’s binomial tree, e.g., self.tree, to find the value of the underlying in each node. Begin with the right-most column, and compute the value of the European Put Option in each node, i.e., MAX(X - S, 0).

    • Work right-to-left to find the option value in each preceding node. The value in each preceding node is the weighted-average of the up and down states reachable from that node.

    Update your __repr__ method to display the option_tree with formatting, e.g.,

    >>> put = BinomialEuroPutOption(25, 22, 0.25, 0.05, 0.03, 1, 1)
    >>> put
    European Put Option Tree (s=25, x=22, stdev=0.25, rf=0.05, div=0.03, t=1 years, nper=1)
      1.14      0.00
                2.14
    

The main difference between American options and European options is that European options are only exercisable at maturity. By contrast, American options are exercisable any time before maturity. That is, the holder of the option may choose to exercise at any time, if this is worth more than holding the option.

For American call options, early exercise is never worthwhile. Rather, the live value of the option is worth more than the early-exercise value. If the owner of the American call option wants out, they are best off simply selling the option. Therefore the value of the American call option is the same as the European call option.

Exercising an American put option might* be worth more than the live value of holding the option to maturity (i.e., European put options, depending on the level of interest rates and dividends). In this case, we need to check at each node whether early-exercise is optimal.

As such, the payoff at each node in the binomial option value tree is given by the maximum of the live-value of the option or the immediate exercise and option payoff:

where Pi,j is the option’s value for node in position i,j of the tree, and depends on the value in the nodes Pi, j+1 and Pi+1, j+1, and Si,j is the value of the underlyingtock in position i,j of the tree.


  1. Write the class definition for the BinomialAmericanPutOption class, which is a subclass of the BinomialOption superclass.

    The BinomialAmericanPutOption will provide a concrete implementation of the option pricing algorithm for an American-style put option, which is exercisable at any time until maturity.

    You do not need to write a constructor for this class! The constructor for the superclass BinomialOption does exactly what we need.

    Write a __repr__ method to produce a string representation of this object. To begin, you may print out the data attributes:

    >>> put = BinomialAmericanPutOption(25, 22, 0.25, 0.05, 0.03, 1, 1)
    >>> put
    American Put Option Tree (s=25, x=22, stdev=0.25, rf=0.05, div=0.03, t=1 years, nper=1)
    

    Next, write the method build_option_tree method, which will calculate the option value at each node in the tree, and save it as a data attribute called option_tree.

    • Create an empty 2-dimensional list of the appropriate dimensions to hold the option values for each node in the tree.

    • Use the superclass’s binomial tree, e.g., self.tree, to find the value of the underlying in each node. Begin with the right-most column, and compute the value of the European Call Option in each node, i.e., MAX(X - S, 0).

    • Work right-to-left to find the option value in each preceding node. The value in each preceding node is the maximum of:

    1 - the “live value” of the option, i.e., weighted-average of the up and down states reachable from that node, discounted to present value.

    2 - the “exercise value” of the option, i.e., the value obtains from immediate exercise in this node: MAX(X - S, 0).

    Update your __repr__ method to display the option_tree with formatting, e.g.,

    >>> put = BinomialAmericanPutOption(25, 22, 0.25, 0.05, 0.03, 1, 1)
    >>> put
    American Put Option Tree (s=25, x=22, stdev=0.25, rf=0.05, div=0.03, t=1 years, nper=1)
      1.14      0.00
                2.14
    

    What’s the difference? Consider these two otherwise identical options:

    >>> put = BinomialEuroPutOption(25, 25, 0.25, 0.06, 0.02, 1, 4)
    >>> put
    European Put Option Tree (s=25, x=25, stdev=0.25, rf=0.06, div=0.02, t=1 years, nper=4)
          1.94      0.68      0.00      0.00      0.00
                    3.12      1.30      0.00      0.00
                              4.81      2.48      0.00
                                        7.01      4.74
                                                  9.22
    >>> put = BinomialAmericanPutOption(25, 25, 0.25, 0.06, 0.02, 1, 4)
    >>> put
    American Put Option Tree (s=25, x=25, stdev=0.25, rf=0.06, div=0.02, t=1 years, nper=4)
          2.03      0.68      0.00      0.00      0.00
                    3.29      1.30      0.00      0.00
                              5.14      2.48      0.00
                                        7.29      4.74
                                                  9.22
    

    The American put option is worth a bit more than they European option, because of the possibility of early exercise.


Submitting Your Work

10 points; will be assignmed by code review

Use the link to GradeScope (left) to submit your work.

Be sure to name your files correctly!

Under the heading for Assignment 7, attach these 2 required files: a6task1.py and a6task2.py.

When you upload the files, the autograder will test your functions/programs.

Warning: Beware of Global print statements

  • The autograder script cannot handle print statements in the global scope, and their inclusion causes this error:

autograder_fail

*   Why does this happen? When the autograder imports your file, the `print` 
    statement(S) execute (at import time), which causes this error.

*   You can prevent this error by not having any `print` statements in the global scope.
    Instead, create an `if __name__ == '__main__':` section at the bottom of the file, and
    put any test cases/print statements in that controlled block. For example:

        if __name__ == '__main__':

            ## put test cases here:
            print('fv_lump_sum(0.05, 2, 100)', fv_lump_sum(0.05, 2, 100))

*   `print` statements inside of functions do not cause this problem.

Notes: