option_lab 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +139 -0
  4. data/.yard/hooks/before_generate.rb +7 -0
  5. data/.yardopts +11 -0
  6. data/Gemfile +26 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +180 -0
  9. data/Rakefile +44 -0
  10. data/docs/OptionLab/BinomialTree.html +1271 -0
  11. data/docs/OptionLab/BjerksundStensland.html +2022 -0
  12. data/docs/OptionLab/BlackScholes.html +2388 -0
  13. data/docs/OptionLab/Engine.html +1716 -0
  14. data/docs/OptionLab/Models/AmericanModelInputs.html +937 -0
  15. data/docs/OptionLab/Models/ArrayInputs.html +463 -0
  16. data/docs/OptionLab/Models/BaseModel.html +223 -0
  17. data/docs/OptionLab/Models/BinomialModelInputs.html +1161 -0
  18. data/docs/OptionLab/Models/BlackScholesInfo.html +967 -0
  19. data/docs/OptionLab/Models/BlackScholesModelInputs.html +851 -0
  20. data/docs/OptionLab/Models/ClosedPosition.html +445 -0
  21. data/docs/OptionLab/Models/EngineData.html +2523 -0
  22. data/docs/OptionLab/Models/EngineDataResults.html +435 -0
  23. data/docs/OptionLab/Models/Inputs.html +2241 -0
  24. data/docs/OptionLab/Models/LaplaceInputs.html +777 -0
  25. data/docs/OptionLab/Models/Option.html +736 -0
  26. data/docs/OptionLab/Models/Outputs.html +1753 -0
  27. data/docs/OptionLab/Models/PoPOutputs.html +645 -0
  28. data/docs/OptionLab/Models/PricingResult.html +848 -0
  29. data/docs/OptionLab/Models/Stock.html +583 -0
  30. data/docs/OptionLab/Models/TreeVisualization.html +688 -0
  31. data/docs/OptionLab/Models.html +251 -0
  32. data/docs/OptionLab/Plotting.html +548 -0
  33. data/docs/OptionLab/Support.html +2884 -0
  34. data/docs/OptionLab/Utils.html +619 -0
  35. data/docs/OptionLab.html +133 -0
  36. data/docs/_index.html +376 -0
  37. data/docs/class_list.html +54 -0
  38. data/docs/css/common.css +1 -0
  39. data/docs/css/full_list.css +58 -0
  40. data/docs/css/style.css +503 -0
  41. data/docs/file.LICENSE.html +70 -0
  42. data/docs/file.README.html +263 -0
  43. data/docs/file_list.html +64 -0
  44. data/docs/frames.html +22 -0
  45. data/docs/index.html +263 -0
  46. data/docs/js/app.js +344 -0
  47. data/docs/js/full_list.js +242 -0
  48. data/docs/js/jquery.js +4 -0
  49. data/docs/method_list.html +1974 -0
  50. data/docs/top-level-namespace.html +110 -0
  51. data/examples/american_options.rb +163 -0
  52. data/examples/covered_call.rb +76 -0
  53. data/lib/option_lab/binomial_tree.rb +238 -0
  54. data/lib/option_lab/bjerksund_stensland.rb +276 -0
  55. data/lib/option_lab/black_scholes.rb +323 -0
  56. data/lib/option_lab/engine.rb +492 -0
  57. data/lib/option_lab/models.rb +768 -0
  58. data/lib/option_lab/plotting.rb +182 -0
  59. data/lib/option_lab/support.rb +471 -0
  60. data/lib/option_lab/utils.rb +107 -0
  61. data/lib/option_lab/version.rb +5 -0
  62. data/lib/option_lab.rb +134 -0
  63. data/option_lab.gemspec +43 -0
  64. metadata +207 -0
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; OptionLab Documentation
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" />
16
+
17
+ <script type="text/javascript">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <h2>Defined Under Namespace</h2>
82
+ <p class="children">
83
+
84
+
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="OptionLab.html" title="OptionLab (module)">OptionLab</a></span>
86
+
87
+
88
+
89
+
90
+ </p>
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ </div>
101
+
102
+ <div id="footer">
103
+ Generated on Sun Apr 27 16:09:33 2025 by
104
+ <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.37 (ruby-3.3.3).
106
+ </div>
107
+
108
+ </div>
109
+ </body>
110
+ </html>
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ # Use the local code instead of the installed gem
5
+ require_relative '../lib/option_lab'
6
+
7
+ # Example of pricing American options using different models
8
+ puts 'American Option Pricing Models Comparison'
9
+ puts '----------------------------------------'
10
+
11
+ # Input parameters
12
+ spot_price = 100.0
13
+ strike_price = 105.0
14
+ risk_free_rate = 0.05
15
+ volatility = 0.25
16
+ dividend_yield = 0.03
17
+ time_to_maturity = 1.0 # 1 year
18
+
19
+ # Calculate option prices using both models
20
+ puts "\nCall Option Comparison:"
21
+ puts '----------------------'
22
+
23
+ # Black-Scholes (European)
24
+ bs_call_price = OptionLab::BlackScholes.get_bs_info(
25
+ spot_price, strike_price, risk_free_rate, volatility, time_to_maturity, dividend_yield
26
+ ).call_price
27
+
28
+ puts "European Call (Black-Scholes): $#{bs_call_price.round(4)}"
29
+
30
+ # Cox-Ross-Rubinstein Binomial Tree (American)
31
+ crr_call_price = OptionLab.price_binomial(
32
+ 'call', spot_price, strike_price, risk_free_rate, volatility, time_to_maturity, 100, true, dividend_yield
33
+ )
34
+
35
+ puts "American Call (CRR Binomial): $#{crr_call_price.round(4)}"
36
+
37
+ # Bjerksund-Stensland (American)
38
+ bs_am_call_price = OptionLab.price_american(
39
+ 'call', spot_price, strike_price, risk_free_rate, volatility, time_to_maturity, dividend_yield
40
+ )
41
+
42
+ puts "American Call (Bjerksund-Stensland): $#{bs_am_call_price.round(4)}"
43
+
44
+ # Put option prices
45
+ puts "\nPut Option Comparison:"
46
+ puts '---------------------'
47
+
48
+ # Black-Scholes (European)
49
+ bs_put_price = OptionLab::BlackScholes.get_bs_info(
50
+ spot_price, strike_price, risk_free_rate, volatility, time_to_maturity, dividend_yield
51
+ ).put_price
52
+
53
+ puts "European Put (Black-Scholes): $#{bs_put_price.round(4)}"
54
+
55
+ # Cox-Ross-Rubinstein Binomial Tree (American)
56
+ crr_put_price = OptionLab.price_binomial(
57
+ 'put', spot_price, strike_price, risk_free_rate, volatility, time_to_maturity, 100, true, dividend_yield
58
+ )
59
+
60
+ puts "American Put (CRR Binomial): $#{crr_put_price.round(4)}"
61
+
62
+ # Bjerksund-Stensland (American)
63
+ bs_am_put_price = OptionLab.price_american(
64
+ 'put', spot_price, strike_price, risk_free_rate, volatility, time_to_maturity, dividend_yield
65
+ )
66
+
67
+ puts "American Put (Bjerksund-Stensland): $#{bs_am_put_price.round(4)}"
68
+
69
+ # Calculate Greeks for the American put option (which typically has higher early exercise value)
70
+ puts "\nAmerican Put Option Greeks Comparison:"
71
+ puts '-----------------------------------'
72
+
73
+ # CRR Binomial Greeks
74
+ crr_greeks = OptionLab.get_binomial_greeks(
75
+ 'put', spot_price, strike_price, risk_free_rate, volatility, time_to_maturity, 100, true, dividend_yield
76
+ )
77
+
78
+ puts 'CRR Binomial Greeks:'
79
+ puts "Delta: #{crr_greeks[:delta].round(6)}"
80
+ puts "Gamma: #{crr_greeks[:gamma].round(6)}"
81
+ puts "Theta: #{crr_greeks[:theta].round(6)}"
82
+ puts "Vega: #{crr_greeks[:vega].round(6)}"
83
+ puts "Rho: #{crr_greeks[:rho].round(6)}"
84
+
85
+ # Bjerksund-Stensland Greeks
86
+ bs_am_greeks = OptionLab.get_american_greeks(
87
+ 'put', spot_price, strike_price, risk_free_rate, volatility, time_to_maturity, dividend_yield
88
+ )
89
+
90
+ puts "\nBjerksund-Stensland Greeks:"
91
+ puts "Delta: #{bs_am_greeks[:delta].round(6)}"
92
+ puts "Gamma: #{bs_am_greeks[:gamma].round(6)}"
93
+ puts "Theta: #{bs_am_greeks[:theta].round(6)}"
94
+ puts "Vega: #{bs_am_greeks[:vega].round(6)}"
95
+ puts "Rho: #{bs_am_greeks[:rho].round(6)}"
96
+
97
+ # Generate binomial tree for visualization (using smaller number of steps)
98
+ tree_data = OptionLab.get_binomial_tree(
99
+ 'put', spot_price, strike_price, risk_free_rate, volatility, time_to_maturity, 5, true, dividend_yield
100
+ )
101
+
102
+ puts "\nBinomial Tree Visualization (5 steps):"
103
+ puts '-------------------------------------'
104
+
105
+ # Display tree parameters
106
+ puts 'Parameters:'
107
+ puts "Spot Price: $#{tree_data[:parameters][:spot_price]}"
108
+ puts "Strike Price: $#{tree_data[:parameters][:strike_price]}"
109
+ puts "Up Factor: #{tree_data[:parameters][:up_factor].round(4)}"
110
+ puts "Down Factor: #{tree_data[:parameters][:down_factor].round(4)}"
111
+ puts "Risk-Neutral Probability: #{tree_data[:parameters][:risk_neutral_probability].round(4)}"
112
+
113
+ puts "\nPrice Tree:"
114
+ tree_data[:stock_prices].each_with_index do |step_prices, step|
115
+ puts "Step #{step}: #{step_prices.take(step + 1).map { |p| p.round(2) }.join(', ')}"
116
+ end
117
+
118
+ puts "\nOption Value Tree:"
119
+ tree_data[:option_values].each_with_index do |step_values, step|
120
+ puts "Step #{step}: #{step_values.take(step + 1).map { |v| v.round(2) }.join(', ')}"
121
+ end
122
+
123
+ puts "\nEarly Exercise Flags:"
124
+ tree_data[:exercise_flags].each_with_index do |step_flags, step|
125
+ puts "Step #{step}: #{step_flags.take(step + 1).join(', ')}"
126
+ end
127
+
128
+ # Early exercise boundary analysis
129
+ puts "\nEarly Exercise Boundary Analysis:"
130
+ puts '--------------------------------'
131
+
132
+ # Test a range of spot prices to find the early exercise boundary
133
+ puts 'Finding early exercise boundary for American put option at various times to maturity:'
134
+ puts "Time\tBoundary"
135
+
136
+ [0.25, 0.5, 0.75, 1.0].each do |t|
137
+ # Binary search to find boundary
138
+ low = 50.0
139
+ high = strike_price
140
+ tolerance = 0.01
141
+
142
+ while (high - low) > tolerance
143
+ mid = (low + high) / 2
144
+
145
+ # Test if this spot price should be exercised early
146
+ tree = OptionLab.get_binomial_tree(
147
+ 'put', mid, strike_price, risk_free_rate, volatility, t, 25, true, dividend_yield
148
+ )
149
+
150
+ if tree[:exercise_flags][0][0]
151
+ # Early exercise is optimal, look higher
152
+ low = mid
153
+ else
154
+ # Early exercise not optimal, look lower
155
+ high = mid
156
+ end
157
+ end
158
+
159
+ boundary = (low + high) / 2
160
+ puts "#{t}\t$#{boundary.round(2)}"
161
+ end
162
+
163
+ puts "\nExample completed successfully."
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ # Use the local code instead of the installed gem
5
+ require_relative '../lib/option_lab'
6
+
7
+ # Example of a covered call strategy
8
+ # A covered call involves owning the underlying stock and selling call options against it
9
+
10
+ # Step 1: Define input parameters
11
+ input_data = {
12
+ stock_price: 168.99, # Current stock price
13
+ volatility: 0.483, # Annualized volatility (48.3%)
14
+ interest_rate: 0.045, # Annualized risk-free interest rate (4.5%)
15
+ start_date: Date.new(2023, 1, 16), # Strategy start date
16
+ target_date: Date.new(2023, 2, 17), # Strategy target date
17
+ min_stock: 68.99, # Minimum stock price for analysis
18
+ max_stock: 268.99, # Maximum stock price for analysis
19
+
20
+ # The covered call strategy is defined with two legs:
21
+ # 1. Long 100 shares of stock
22
+ # 2. Short 1 call option with strike price 185.0 and premium 4.1
23
+ strategy: [
24
+ { type: 'stock', n: 100, action: 'buy' },
25
+ {
26
+ type: 'call',
27
+ strike: 185.0,
28
+ premium: 4.1,
29
+ n: 100,
30
+ action: 'sell',
31
+ expiration: Date.new(2023, 2, 17),
32
+ },
33
+ ],
34
+ }
35
+
36
+ # Step 2: Run the strategy calculation
37
+ puts 'Calculating strategy outcomes...'
38
+ outputs = OptionLab.run_strategy(input_data)
39
+
40
+ # Step 3: Print the results
41
+ puts "\nCovered Call Strategy Results:"
42
+ puts '------------------------------'
43
+ puts outputs
44
+
45
+ # Step 4: Extract and display key metrics
46
+ puts "\nKey Metrics:"
47
+ puts "Probability of Profit: #{(outputs.probability_of_profit * 100).round(2)}%"
48
+ puts "Expected Profit (when profitable): $#{outputs.expected_profit}" if outputs.expected_profit
49
+ puts "Expected Loss (when unprofitable): $#{outputs.expected_loss}" if outputs.expected_loss
50
+ puts "Strategy Cost: $#{outputs.strategy_cost}"
51
+ puts "Maximum Return: $#{outputs.maximum_return_in_the_domain}"
52
+ puts "Minimum Return: $#{outputs.minimum_return_in_the_domain}"
53
+
54
+ # Step 5: Print Greeks for each leg
55
+ puts "\nGreeks by Strategy Leg:"
56
+ puts 'Leg 1 (Stock):'
57
+ puts " Delta: #{outputs.delta[0]}"
58
+
59
+ puts 'Leg 2 (Call Option):'
60
+ puts " Delta: #{outputs.delta[1]}"
61
+ puts " Gamma: #{outputs.gamma[1]}"
62
+ puts " Theta: #{outputs.theta[1]}"
63
+ puts " Vega: #{outputs.vega[1]}"
64
+ puts " Rho: #{outputs.rho[1]}"
65
+ puts " Implied Volatility: #{(outputs.implied_volatility[1] * 100).round(2)}%"
66
+ puts " ITM Probability: #{(outputs.in_the_money_probability[1] * 100).round(2)}%"
67
+
68
+ # Step 6: Export P/L data to CSV
69
+ csv_filename = 'covered_call_pl.csv'
70
+ OptionLab.pl_to_csv(outputs, csv_filename)
71
+ puts "\nProfit/Loss data exported to #{csv_filename}"
72
+
73
+ # Step 7: Plot the profit/loss diagram
74
+ puts "\nGenerating profit/loss diagram..."
75
+ OptionLab.plot_pl(outputs)
76
+ puts 'Plot displayed. Close the plot window to exit.'
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'numo/narray'
4
+
5
+ module OptionLab
6
+
7
+ # Implementation of the Cox-Ross-Rubinstein (CRR) binomial tree model
8
+ # for American and European options pricing
9
+ module BinomialTree
10
+
11
+ class << self
12
+
13
+ # Price an option using the Cox-Ross-Rubinstein binomial tree model
14
+ # @param option_type [String] 'call' or 'put'
15
+ # @param s0 [Float] Spot price
16
+ # @param x [Float] Strike price
17
+ # @param r [Float] Risk-free interest rate
18
+ # @param volatility [Float] Volatility
19
+ # @param years_to_maturity [Float] Time to maturity in years
20
+ # @param n_steps [Integer] Number of time steps
21
+ # @param is_american [Boolean] True for American options, false for European
22
+ # @param dividend_yield [Float] Continuous dividend yield
23
+ # @return [Float] Option price
24
+ def price_option(option_type, s0, x, r, volatility, years_to_maturity, n_steps = 100, is_american = true, dividend_yield = 0.0)
25
+ # Calculate time step
26
+ dt = years_to_maturity / n_steps.to_f
27
+
28
+ # Calculate up and down factors
29
+ u = Math.exp(volatility * Math.sqrt(dt))
30
+ d = 1.0 / u
31
+
32
+ # Calculate risk-neutral probability
33
+ effective_r = r - dividend_yield
34
+ p = (Math.exp(effective_r * dt) - d) / (u - d)
35
+
36
+ # Calculate discount factor
37
+ discount = Math.exp(-r * dt)
38
+
39
+ # Initialize price tree
40
+ stock_prices = Array.new(n_steps + 1) { Array.new(n_steps + 1, 0.0) }
41
+ option_values = Array.new(n_steps + 1) { Array.new(n_steps + 1, 0.0) }
42
+
43
+ # Fill stock price tree
44
+ (0..n_steps).each do |i|
45
+ (0..i).each do |j|
46
+ stock_prices[i][j] = s0 * (u**(i - j)) * (d**j)
47
+ end
48
+ end
49
+
50
+ # Calculate option values at expiration (i = n_steps)
51
+ (0..n_steps).each do |j|
52
+ option_values[n_steps][j] = option_payoff(option_type, stock_prices[n_steps][j], x)
53
+ end
54
+
55
+ # Work backwards through the tree
56
+ (n_steps - 1).downto(0) do |i|
57
+ (0..i).each do |j|
58
+ # Expected value (European option value)
59
+ expected_value = discount * (p * option_values[i + 1][j] + (1 - p) * option_values[i + 1][j + 1])
60
+
61
+ if is_american
62
+ # For American options, compare with immediate exercise value
63
+ exercise_value = option_payoff(option_type, stock_prices[i][j], x)
64
+ option_values[i][j] = [expected_value, exercise_value].max
65
+ else
66
+ # For European options, use expected value
67
+ option_values[i][j] = expected_value
68
+ end
69
+ end
70
+ end
71
+
72
+ # Root node contains the option price
73
+ option_values[0][0]
74
+ end
75
+
76
+ # Calculate option payoff at expiration
77
+ # @param option_type [String] 'call' or 'put'
78
+ # @param stock_price [Float] Stock price
79
+ # @param strike [Float] Strike price
80
+ # @return [Float] Option payoff
81
+ def option_payoff(option_type, stock_price, strike)
82
+ if option_type == 'call'
83
+ [stock_price - strike, 0.0].max
84
+ elsif option_type == 'put'
85
+ [strike - stock_price, 0.0].max
86
+ else
87
+ raise ArgumentError, "Option type must be either 'call' or 'put'!"
88
+ end
89
+ end
90
+
91
+ # Get a full binomial tree as a structured output
92
+ # @param option_type [String] 'call' or 'put'
93
+ # @param s0 [Float] Spot price
94
+ # @param x [Float] Strike price
95
+ # @param r [Float] Risk-free interest rate
96
+ # @param volatility [Float] Volatility
97
+ # @param years_to_maturity [Float] Time to maturity in years
98
+ # @param n_steps [Integer] Number of time steps
99
+ # @param is_american [Boolean] True for American options, false for European
100
+ # @param dividend_yield [Float] Continuous dividend yield
101
+ # @return [Hash] Tree structure with stock prices and option values
102
+ def get_tree(option_type, s0, x, r, volatility, years_to_maturity, n_steps = 15, is_american = true, dividend_yield = 0.0)
103
+ # Calculate time step
104
+ dt = years_to_maturity / n_steps.to_f
105
+
106
+ # Calculate up and down factors
107
+ u = Math.exp(volatility * Math.sqrt(dt))
108
+ d = 1.0 / u
109
+
110
+ # Calculate risk-neutral probability
111
+ effective_r = r - dividend_yield
112
+ p = (Math.exp(effective_r * dt) - d) / (u - d)
113
+
114
+ # Calculate discount factor
115
+ discount = Math.exp(-r * dt)
116
+
117
+ # Initialize price tree
118
+ stock_prices = Array.new(n_steps + 1) { Array.new(n_steps + 1, 0.0) }
119
+ option_values = Array.new(n_steps + 1) { Array.new(n_steps + 1, 0.0) }
120
+ exercise_flags = Array.new(n_steps + 1) { Array.new(n_steps + 1, false) }
121
+
122
+ # Fill stock price tree
123
+ (0..n_steps).each do |i|
124
+ (0..i).each do |j|
125
+ stock_prices[i][j] = s0 * (u**(i - j)) * (d**j)
126
+ end
127
+ end
128
+
129
+ # Calculate option values at expiration (i = n_steps)
130
+ (0..n_steps).each do |j|
131
+ option_values[n_steps][j] = option_payoff(option_type, stock_prices[n_steps][j], x)
132
+ end
133
+
134
+ # Work backwards through the tree
135
+ (n_steps - 1).downto(0) do |i|
136
+ (0..i).each do |j|
137
+ # Expected value (European option value)
138
+ expected_value = discount * (p * option_values[i + 1][j] + (1 - p) * option_values[i + 1][j + 1])
139
+
140
+ if is_american
141
+ # For American options, compare with immediate exercise value
142
+ exercise_value = option_payoff(option_type, stock_prices[i][j], x)
143
+
144
+ if exercise_value > expected_value
145
+ option_values[i][j] = exercise_value
146
+ exercise_flags[i][j] = true
147
+ else
148
+ option_values[i][j] = expected_value
149
+ end
150
+ else
151
+ # For European options, use expected value
152
+ option_values[i][j] = expected_value
153
+ end
154
+ end
155
+ end
156
+
157
+ # Return tree structure
158
+ {
159
+ stock_prices: stock_prices,
160
+ option_values: option_values,
161
+ exercise_flags: exercise_flags,
162
+ parameters: {
163
+ option_type: option_type,
164
+ spot_price: s0,
165
+ strike_price: x,
166
+ risk_free_rate: r,
167
+ volatility: volatility,
168
+ time_to_maturity: years_to_maturity,
169
+ steps: n_steps,
170
+ is_american: is_american,
171
+ dividend_yield: dividend_yield,
172
+ up_factor: u,
173
+ down_factor: d,
174
+ risk_neutral_probability: p,
175
+ },
176
+ }
177
+ end
178
+
179
+ # Calculate option Greeks using the CRR model and finite difference methods
180
+ # @param option_type [String] 'call' or 'put'
181
+ # @param s0 [Float] Spot price
182
+ # @param x [Float] Strike price
183
+ # @param r [Float] Risk-free interest rate
184
+ # @param volatility [Float] Volatility
185
+ # @param years_to_maturity [Float] Time to maturity in years
186
+ # @param n_steps [Integer] Number of time steps
187
+ # @param is_american [Boolean] True for American options, false for European
188
+ # @param dividend_yield [Float] Continuous dividend yield
189
+ # @return [Hash] Option Greeks (delta, gamma, theta, vega, rho)
190
+ def get_greeks(option_type, s0, x, r, volatility, years_to_maturity, n_steps = 100, is_american = true, dividend_yield = 0.0)
191
+ # Small increment for finite difference calculation
192
+ h_s = s0 * 0.001 # For Delta and Gamma
193
+ h_t = 1.0 / 365 # For Theta (1 day)
194
+ h_v = 0.001 # For Vega
195
+ h_r = 0.0001 # For Rho
196
+
197
+ # Base price
198
+ price = price_option(option_type, s0, x, r, volatility, years_to_maturity, n_steps, is_american, dividend_yield)
199
+
200
+ # Delta: ∂V/∂S
201
+ price_up = price_option(option_type, s0 + h_s, x, r, volatility, years_to_maturity, n_steps, is_american, dividend_yield)
202
+ price_down = price_option(option_type, s0 - h_s, x, r, volatility, years_to_maturity, n_steps, is_american, dividend_yield)
203
+ delta = (price_up - price_down) / (2 * h_s)
204
+
205
+ # Gamma: ∂²V/∂S²
206
+ gamma = (price_up - 2 * price + price_down) / (h_s * h_s)
207
+
208
+ # Theta: -∂V/∂t
209
+ price_t_down = if years_to_maturity - h_t > 0
210
+ price_option(option_type, s0, x, r, volatility, years_to_maturity - h_t, n_steps, is_american, dividend_yield)
211
+ else
212
+ option_payoff(option_type, s0, x)
213
+ end
214
+ theta = -(price_t_down - price) / h_t
215
+
216
+ # Vega: ∂V/∂σ
217
+ price_v_up = price_option(option_type, s0, x, r, volatility + h_v, years_to_maturity, n_steps, is_american, dividend_yield)
218
+ vega = (price_v_up - price) / h_v
219
+
220
+ # Rho: ∂V/∂r
221
+ price_r_up = price_option(option_type, s0, x, r + h_r, volatility, years_to_maturity, n_steps, is_american, dividend_yield)
222
+ rho = (price_r_up - price) / h_r
223
+
224
+ # Return Greeks
225
+ {
226
+ delta: delta,
227
+ gamma: gamma,
228
+ theta: theta,
229
+ vega: vega,
230
+ rho: rho,
231
+ }
232
+ end
233
+
234
+ end
235
+
236
+ end
237
+
238
+ end