Loading [MathJax]/extensions/tex2jax.js
MRFFL: MR Fortran Finance Library 2024-12-28
Computational Tools For Finance
All Namespaces Files Functions Variables
mrffl_stats.f90
Go to the documentation of this file.
1! -*- Mode:F90; Coding:us-ascii-unix; fill-column:129 -*-
2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!.H.S.!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!.H.E.!!
3!>
4!! @file mrffl_stats.f90
5!! @author Mitch Richling http://www.mitchr.me/
6!! @date 2024-12-19
7!! @brief Statstical tools supporting MRFFL.@EOL
8!! @keywords finance fortran monte carlo inflation cashflow time value of money tvm percentages taxes stock market
9!! @std F2023
10!! @see https://github.com/richmit/FortranFinance
11!! @copyright
12!! @parblock
13!! Copyright (c) 2024, Mitchell Jay Richling <http://www.mitchr.me/> All rights reserved.
14!!
15!! Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
16!! conditions are met:
17!!
18!! 1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following
19!! disclaimer.
20!!
21!! 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following
22!! disclaimer in the documentation and/or other materials provided with the distribution.
23!!
24!! 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
25!! derived from this software without specific prior written permission.
26!!
27!! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
28!! INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29!! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30!! EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
31!! USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32!! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
33!! OF THE POSSIBILITY OF SUCH DAMAGE.
34!! @endparblock
35!!
36
37!----------------------------------------------------------------------------------------------------------------------------------
38!> Some statstical utilities supporting other MRFFL modules.
39!!
41 use mrffl_config, only: rk=>mrfflrk, ik=>mrfflik, zero_epsilon
42 implicit none
43 private
44
45 public :: mean_and_variance
46 public :: rand_int, rand_real ! Function wrapper on built in PRNG
47 public :: resample_tail, resample_head ! Use rand_int
48 public :: rand_norm_std_box, rand_norm_std_probit, rand_norm_std_probit_clip ! Use: rand_real -- fundamental normal generators
49 public :: rand_norm_std ! Use: rand_norm_std_*
50 public :: rand_norm, rand_log_norm ! Use: rand_norm_std
51 public :: probit
53
54contains
55
56 !--------------------------------------------------------------------------------------------------------------------------------
57 !> Return random integer in U([optional_lower_bound,upper_bound)) -- optional_lower_bound is 0 if missing.
58 !!
59 !! @param upper_bound Upper bound for random number
60 !! @param optional_lower_bound Lower bound for random number
61 !!
62 integer(kind=ik) function rand_int(upper_bound, optional_lower_bound)
63 implicit none
64 integer(kind=ik), intent(in) :: upper_bound
65 integer(kind=ik), optional, intent(in) :: optional_lower_bound
66 integer(kind=ik) :: lower_bound = 0
67 real(kind=rk) :: r
68 if (present(optional_lower_bound)) lower_bound = optional_lower_bound
69 if (lower_bound > upper_bound) then
70 error stop "ERROR(rand_int): lower_bound > upper_bound!"
71 end if
72 call random_number(r) ! Random Number in [0, 1)
73 rand_int = lower_bound + int(r * (upper_bound - lower_bound), kind=ik)
74 end function rand_int
75
76 !--------------------------------------------------------------------------------------------------------------------------------
77 !> Return random real value in U([optional_lower_bound,upper_bound)) -- optional_lower_bound is 0 if missing.
78 !!
79 !! @param upper_bound Upper bound for random number
80 !! @param optional_lower_bound Lower bound for random number
81 !!
82 real(kind=rk) function rand_real(upper_bound, optional_lower_bound)
83 implicit none
84 real(kind=rk), intent(in) :: upper_bound
85 real(kind=rk), optional, intent(in) :: optional_lower_bound
86 real(kind=rk) :: lower_bound = 0
87 real(kind=rk) :: r
88 if (present(optional_lower_bound)) lower_bound = optional_lower_bound
89 if (lower_bound > upper_bound) then
90 error stop "ERROR(rand_real): lower_bound > upper_bound!"
91 end if
92 call random_number(r) ! Random Number in [0, 1)
93 rand_real = lower_bound + r * (upper_bound - lower_bound)
94 end function rand_real
95
96 !--------------------------------------------------------------------------------------------------------------------------------
97 !> Return random value from among the last tail_length elements of data.
98 !!
99 !! @param data The data set -- a rank 1 array.
100 !! @param tail_length Number of elements to consider for resample.
101 !!
102 real(kind=rk) function resample_tail(data, tail_length)
103 implicit none
104 real(kind=rk), intent(in) :: data(:)
105 integer(kind=ik), intent(in) :: tail_length
106 integer(kind=ik) :: i
107 i = ubound(data, 1, kind=ik) - rand_int(min(tail_length, int(size(data), kind=ik)))
108 i = max(i, lbound(data, 1, kind=ik))
109 i = min(i, ubound(data, 1, kind=ik))
110 resample_tail = data(i)
111 end function resample_tail
112
113 !--------------------------------------------------------------------------------------------------------------------------------
114 !> Return random value from among the first head_length elements of data.
115 !!
116 !! @param data The data set -- a rank 1 array.
117 !! @param head_length Number of elements to consider for resample.
118 !!
119 real(kind=rk) function resample_head(data, head_length)
120 implicit none
121 real(kind=rk), intent(in) :: data(:)
122 integer(kind=ik), intent(in) :: head_length
123 integer(kind=ik) :: i
124 i = lbound(data, 1, kind=ik) + rand_int(min(head_length, size(data, kind=ik)))
125 i = max(i, lbound(data, 1, kind=ik))
126 i = min(i, ubound(data, 1, kind=ik))
127 resample_head = data(i)
128 end function resample_head
129
130 !--------------------------------------------------------------------------------------------------------------------------------
131 !> Return mean & variance of elements in an array.
132 !!
133 !! @param mean Holds the mean upon exit.
134 !! @param variance Holds the variance (standard deviation squared) upon exit.
135 !! @param data The data set -- a rank 1 array.
136 !!
137 subroutine mean_and_variance(mean, variance, data)
138 implicit none
139 real(kind=rk), intent(in) :: data(:)
140 real(kind=rk), intent(out) :: mean, variance
141 mean = sum(data) / size(data)
142 variance = sum((data - mean) ** 2)
143 end subroutine mean_and_variance
144
145 !--------------------------------------------------------------------------------------------------------------------------------
146 !> Return random value from the standard normal distribution.
147 !!
148 !! This function simply calls the preferred rand_norm_std_* function. Currently that's rand_norm_std_probit().
149 !!
150 real(kind=rk) function rand_norm_std()
151 implicit none
153 end function rand_norm_std
154
155 !--------------------------------------------------------------------------------------------------------------------------------
156 !> Return random value from the standard normal distribution using the Box-Muller transform.
157 !!
158 !! Reference:
159 !! Box, G. E. P., and Mervin E. Muller. 1958. "A Note on the Generation of Random Normal Deviates." The Annals of Mathematical Statistics 29 (2): 610-11.
160 !!
161 real(kind=rk) function rand_norm_std_box()
162 implicit none
163 real(kind=rk), parameter :: pi = 4.0_rk * atan(1.0_rk)
164 real(kind=rk) :: u, v
165 logical, save :: cached_valid = .false.
166 real(kind=rk), save :: cached_value = 0.0_rk
167 if (cached_valid) then
168 rand_norm_std_box = cached_value
169 cached_valid = .false.
170 else
171 call random_number(u)
172 call random_number(v)
173 rand_norm_std_box = sqrt(-2 * log(u)) * cos(2 * pi * v)
174 cached_value = sqrt(-2 * log(u)) * sin(2 * pi * v)
175 cached_valid = .true.
176 end if
177 end function rand_norm_std_box
178
179 !--------------------------------------------------------------------------------------------------------------------------------
180 !> Return random value from the standard normal distribution in [-5.612, 5.612] using the Probit function.
181 !!
182 real(kind=rk) function rand_norm_std_probit_clip()
183 implicit none
184 real(kind=rk) :: u
185 do
186 call random_number(u)
187 if ((u > zero_epsilon) .and. (u < (1.0_rk - zero_epsilon))) then
189 return
190 end if
191 end do
192 end function rand_norm_std_probit_clip
193
194 !--------------------------------------------------------------------------------------------------------------------------------
195 !> Return random value from the standard normal distribution using the Probit function.
196 !!
197 real(kind=rk) function rand_norm_std_probit()
198 implicit none
199 real(kind=rk) :: u
200 do
201 call random_number(u)
202 if (u < 1.0_rk) then
204 return
205 end if
206 end do
207 end function rand_norm_std_probit
208
209 !--------------------------------------------------------------------------------------------------------------------------------
210 !> Return random value from the specified normal distribution.
211 !!
212 !! @param mean Mean of the distribution
213 !! @param variance Variance (standard deviation squared) of the distribution
214 !!
215 real(kind=rk) function rand_norm(mean, variance)
216 implicit none
217 real(kind=rk), intent(in) :: mean, variance
218 rand_norm = rand_norm_std() * variance + mean
219 end function rand_norm
220
221 !--------------------------------------------------------------------------------------------------------------------------------
222 !> Return random value from the specified log-normal distribution.
223 !!
224 !! @f[ \text{mean} = e^\left(\mu+\frac{\sigma^2}{2}\right) @f]
225 !! @f[ \text{median} = e^\mu @f]
226 !! @f[ \text{variance} = \left[e^{\sigma^2}-1\right] e^{2\mu+\sigma^2} @f]
227 !!
228 !! @param mu Mean of the distribution
229 !! @param sigma Standard deviation of the distribution
230 !!
231 real(kind=rk) function rand_log_norm(mu, sigma)
232 implicit none
233 real(kind=rk), intent(in) :: mu, sigma
234 rand_log_norm = exp(rand_norm_std() * sigma + mu)
235 end function rand_log_norm
236
237 !--------------------------------------------------------------------------------------------------------------------------------
238 !> Probit function -- i.e. inverse of standard normal CDF.
239 !! Return $z_p$ such that @f$ P(\mathcal{N}(0, 1)<=z_p) = p @f$
240 !!
241 !! Reference:
242 !! Wichura, Michael J. 1988. "Algorithm AS 241: The Percentage Points of the Normal Distribution." Journal of the Royal Statistical Society. Series C (Applied Statistics) 37 (3): 477-84.
243 !!
244 !! @param p Probablity in @f$ (0,1) @f$.
245 !!
246 real(kind=rk) function probit(p)
247 implicit none
248 real(kind=rk), intent(in) :: p
249 real(kind=rk) :: q
250 real(kind=rk) :: r
251 real(kind=rk), parameter :: const1 = 0.180625_rk
252 real(kind=rk), parameter :: const2 = 1.600000_rk
253 real(kind=rk), parameter :: split1 = 0.425000_rk
254 real(kind=rk), parameter :: split2 = 5.000000_rk
255 real(kind=rk), parameter :: r1_numr(8) = [ & ! Coefficients for p close to 0.5
256 2.50908092873012267270e+03_rk, 3.34305755835881281050e+04_rk, 6.72657709270087008530e+04_rk, 4.59219539315498714570e+04_rk, &
257 1.37316937655094611250e+04_rk, 1.97159095030655144270e+03_rk, 1.33141667891784377450e+02_rk, 3.38713287279636660800e+00_rk ]
258 real(kind=rk), parameter :: r1_dnom(8) = [ &
259 5.22649527885285456100e+03_rk, 2.87290857357219426740e+04_rk, 3.93078958000927106100e+04_rk, 2.12137943015865958670e+04_rk, &
260 5.39419602142475110770e+03_rk, 6.87187007492057908300e+02_rk, 4.23133307016009112520e+01_rk, 1.00000000000000000000e+00_rk ]
261 real(kind=rk), parameter :: r2_numr(8) = [ & ! Coefficients for p not near 0.5, 0, or 1
262 7.74545014278341407640e-04_rk, 2.27238449892691845833e-02_rk, 2.41780725177450611770e-01_rk, 1.27045825245236838258e+00_rk, &
263 3.64784832476320460504e+00_rk, 5.76949722146069140550e+00_rk, 4.63033784615654529590e+00_rk, 1.42343711074968357734e+00_rk ]
264 real(kind=rk), parameter :: r2_dnom(8) = [ &
265 1.05075007164441684324e-09_rk, 5.47593808499534494600e-04_rk, 1.51986665636164571966e-02_rk, 1.48103976427480074590e-01_rk, &
266 6.89767334985100004550e-01_rk, 1.67638483018380384940e+00_rk, 2.05319162663775882187e+00_rk, 1.00000000000000000000e+00_rk ]
267 real(kind=rk), parameter :: r3_numr(8) = [ & ! Coefficients for p close 0 or 1
268 2.01033439929228813265e-07_rk, 2.71155556874348757815e-05_rk, 1.24266094738807843860e-03_rk, 2.65321895265761230930e-02_rk, &
269 2.96560571828504891230e-01_rk, 1.78482653991729133580e+00_rk, 5.46378491116411436990e+00_rk, 6.65790464350110377720e+00_rk ]
270 real(kind=rk), parameter :: r3_dnom(8) = [ &
271 2.04426310338993978564e-15_rk, 1.42151175831644588870e-07_rk, 1.84631831751005468180e-05_rk, 7.86869131145613259100e-04_rk, &
272 1.48753612908506148525e-02_rk, 1.36929880922735805310e-01_rk, 5.99832206555887937690e-01_rk, 1.00000000000000000000e+00_rk ]
273 if (p <= 0.0_rk) then ! $ p\in(\infty, 0] $
274 probit = -huge(p)
275 else if (1.0_rk <= p) then ! $ p\in[1, \infty) $
276 probit = huge(p)
277 else
278 q = p - 0.5_rk
279 if(abs(q) <= split1) then ! $ p\in[0.075, 0.925] $
280 r = const1 - q * q
281 probit = q * poly_eval(r1_numr, r) / poly_eval(r1_dnom, r)
282 else ! $ NOT p\in[0.075, 0.925] $
283 if (q < 0.0_rk) then ! $ p\in[0, 0.075) $
284 r = p
285 else ! $ p\in(0.925, 1])
286 r = 1.0_rk - p
287 end if
288 r = sqrt(-log(abs(r))) ! p in ( 1.3887943865e-11, 0.999999999986)
289 if (r <= split2) then
290 r = r - const2
291 probit = poly_eval(r2_numr, r) / poly_eval(r2_dnom, r)
292 else ! p is close to 0 or 1
293 r = r - split2
294 probit = poly_eval(r3_numr, r) / poly_eval(r3_dnom, r)
295 end if
296 if (q < 0.0_rk) then
297 probit = - probit
298 end if
299 end if
300 end if
301 end function probit
302
303 !--------------------------------------------------------------------------------------------------------------------------------
304 !> Evaluate a univariate polynomial.
305 !!
306 !! Used by probit. Not exported from the module.
307 !!
308 !! The polynomial is ordered in the natural way with the highest coefficient first in the array:
309 !! @f[ p=\sum_{k=1}^{d+1} p_kx^{1+d-k} @f]
310 !! Note that $k$ in the above formula is the index in the array p.
311 !!
312 !! @param p A $d+1$ element, rank 1 array holding the coefficients of a polynomial
313 !! @param x The value at which to evaluate the polynomial
314 !!
315 real(kind=rk) function poly_eval(p, x)
316 implicit none
317 real(kind=rk), intent(in) :: p(:)
318 real(kind=rk), intent(in) :: x
319 integer i
320 poly_eval = p(1)
321 do i = 2,size(p)
322 poly_eval = poly_eval * x + p(i)
323 end do
324 end function poly_eval
325
326 !--------------------------------------------------------------------------------------------------------------------------------
327 !> Compute a set of random steps in a GBM process.
328 !!
329 !! @param s0 Initial value of an asset
330 !! @param mu Mean gain for the assest over 1 unit of time
331 !! @param sigma Standard deviation of gain for the assest over 1 unit of time
332 !! @param step_values A rank 1 array which will hold the values of the asset over time
333 !!
334 subroutine geometric_brownian_motion(step_values, s0, mu, sigma)
335 real(kind=rk), intent(out) :: step_values(:)
336 real(kind=rk), intent(in) :: s0, mu, sigma
337 real(kind=rk) :: step_width, m, s, sum
338 integer :: i, num_steps
339 num_steps = size(step_values)
340 step_width = 1.0_rk / (num_steps-1)
341 m = (mu - 0.5 * sigma ** 2) * step_width
342 s = sigma * sqrt(step_width)
343 sum = 0
344 do i=1,num_steps
345 sum = sum + rand_norm(m, s)
346 step_values(i) = s0 * exp(sum)
347 end do
348 end subroutine geometric_brownian_motion
349
350 !--------------------------------------------------------------------------------------------------------------------------------
351 !> Compute a set of random steps in a zero clipped browinan motion.
352 !!
353 !! @param s0 Initial value of an asset
354 !! @param mu Mean gain for the assest over 1 unit of time
355 !! @param sigma Standard deviation of gain for the assest over 1 unit of time
356 !! @param step_values A rank 1 array which will hold the values of the asset over time
357 !!
358 subroutine zero_clipped_brownian_motion(step_values, s0, mu, sigma)
359 real(kind=rk), intent(out) :: step_values(:)
360 real(kind=rk), intent(in) :: s0, mu, sigma
361 real(kind=rk) :: step_width, m, s
362 integer :: i, num_steps
363 num_steps = size(step_values)
364 step_width = 1.0_rk / (num_steps-1)
365 m = mu * step_width
366 s = sigma * sqrt(step_width)
367 step_values(1) = s0
368 do i=2,num_steps
369 if (abs(step_values(i-1)) < zero_epsilon) then
370 step_values(i) = 0.0_rk
371 else
372 step_values(i) = max(step_values(i-1) * (1 + rand_norm(m, s)), 0.0_rk)
373 end if
374 end do
375 end subroutine zero_clipped_brownian_motion
376
377end module mrffl_stats
Configuration for MRFFL (MR Fortran Finance Library).
integer, parameter, public mrfflrk
Real kind used in interfaces.
real(kind=mrfflrk), parameter, public zero_epsilon
Used to test for zero.
integer, parameter, public mrfflik
Integer kinds used in interfaces.
Some statstical utilities supporting other MRFFL modules.
real(kind=rk) function, public resample_head(data, head_length)
Return random value from among the first head_length elements of data.
real(kind=rk) function, public resample_tail(data, tail_length)
Return random value from among the last tail_length elements of data.
real(kind=rk) function, public probit(p)
Probit function – i.e.
subroutine, public zero_clipped_brownian_motion(step_values, s0, mu, sigma)
Compute a set of random steps in a zero clipped browinan motion.
subroutine, public mean_and_variance(mean, variance, data)
Return mean & variance of elements in an array.
real(kind=rk) function, public rand_log_norm(mu, sigma)
Return random value from the specified log-normal distribution.
real(kind=rk) function, public rand_norm_std_box()
Return random value from the standard normal distribution using the Box-Muller transform.
integer(kind=ik) function, public rand_int(upper_bound, optional_lower_bound)
Return random integer in U([optional_lower_bound,upper_bound)) – optional_lower_bound is 0 if missing...
real(kind=rk) function, public rand_real(upper_bound, optional_lower_bound)
Return random real value in U([optional_lower_bound,upper_bound)) – optional_lower_bound is 0 if miss...
real(kind=rk) function poly_eval(p, x)
Evaluate a univariate polynomial.
real(kind=rk) function, public rand_norm_std_probit()
Return random value from the standard normal distribution using the Probit function.
real(kind=rk) function, public rand_norm_std_probit_clip()
Return random value from the standard normal distribution in [-5.612, 5.612] using the Probit functio...
subroutine, public geometric_brownian_motion(step_values, s0, mu, sigma)
Compute a set of random steps in a GBM process.
real(kind=rk) function, public rand_norm_std()
Return random value from the standard normal distribution.
real(kind=rk) function, public rand_norm(mean, variance)
Return random value from the specified normal distribution.