monte_carlo#
Series: lecture-python-intro
File:
lectures/monte_carlo.mdAudit date: 2026-08-26
Corpus snapshot:
a12d17c0efCategories audited: writing, math, code, figures, links, admonitions (JAX out of scope)
Overall score: 7.7 / 10
Priority: LOW
Score breakdown#
Category |
Score |
One-line note |
|---|---|---|
Writing |
5.5/10 |
|
Math |
5/10 |
|
Code |
7.5/10 |
|
JAX |
out of scope |
JAX rules target |
Figures |
8/10 |
|
References |
N/A |
no citations in this lecture. |
Links |
10/10 |
no mechanical violations detected. |
Admonitions |
10/10 |
no mechanical violations detected. |
Issues#
Critical#
None found.
High severity#
[qe-code-004] — Use quantecon Timer context manager. Count: 8. Lines: 172, 229, 241, 610, 656, 667, 741, 777. Example: %%time.
[qe-math-010 (proposed)] — Blackboard \mathbb{P}, \mathbb{E}, \mathbb{V} with braces. Count: 10. Lines: 94, 101, 168, 273, 290, 341, 371, 382, 575. Example: missing braces:
\mathbb E.
Medium severity#
[qe-code-001] (reviewer) — Follow PEP8 unless closer to mathematical notation. Count: 4. Lines: 191, 495, 526, 635. Example: 635-643 mis-indents a whole signature:
def compute_call_price_vector(opens its parenthesis at column 29, so a visual indent is column 30, and the nine continuation lines sit at column 23 - the alignment ofdef compute_call_price(at 584, from which the function was copied. 526-531 is off in the other direction, at column 31 against a parenthesis at 29. 191 closes thecompute_meansignature with):at four spaces, which is neither the indentation of the line that opens the construct nor the indentation of the parameters. And 495-499 (repeated at 693-697) pads before the assignment operator to align a block of constants -default_μ = 0.0001,default_S0 = 10- which is the “more than one space around an assignment operator to align it with another” that PEP8 lists under Pet Peeves. Two related items that are not spacing:return (S / n)at 198 wraps its return value in parentheses for nothing, andreturn_drawsat 411 names an array of payoffs,np.maximum(S - K, 0), after returns.[qe-fig-003] — No matplotlib embedded titles. Count: 1. Lines: 556. Example: .set_title.
[qe-math-009] (reviewer) — Choose simplicity in mathematical notation. Count: 2. Lines: 101, 273. Example: the mean and the variance of the same random variable, two displays apart, are written in two different notational systems:
\mathbb E Sat 94 and\mathop{\mathrm{Var}} Sat 101, where the second hand-rolls an operator with\mathop{}that\operatorname{Var}(or\mathbb V, to match the expectation) gives for free. 273 mismatches its own delimiters inside a single line:\mathbb P\left\{G = 10^6 \right\} = \mathbb P\{G = 0\}-\left\{ \right\}on the left of the equals sign and plain\{ \}on the right, for contents that are one line tall either way. A third, smaller item:LNis set as maths italic at 87, 125 and 377, so the distribution’s name reads as a product \(L \cdot N\) where\mathrm{LN}would set it upright.[qe-writing-002] (reviewer) — Keep writing clear, concise, and valuable. Count: 3. Lines: 80, 188, 467. Example: 188-198 restates 174-180 exactly. The loop body inside
compute_meanis the same six lines as the bare loop above it, character for character; the function is called once at 206, never timed, and never used again, so the reader reads the same code twice and learns nothing from the second reading - the vectorized comparison at 219-243 is what the section is actually building to. 80-81 (“Either way, this discussion shows the importance of understanding the distribution of \(S\)”) closes a run of nine one-sentence paragraphs by restating their premise. And 464-467 says the same thing twice in two sentences and gets the second one wrong grammatically: “volatility (measured by \(\sigma\)) is not stationary” followed by “Instead it rather changes over time, sometimes high (like during the GFC) and sometimes low” - where “Instead it rather changes” needs an editor and “GFC” is used unexpanded in an introductory lecture with an international audience.[qe-writing-003] (reviewer) — Maintain logical flow. Count: 4. Lines: 497, 668, 685, 696. Example: 668 breaks the exercise it is solving.
monte_carlo_ex1asks the reader to “write a faster version of this code using NumPy” (626); the solution definescompute_call_price_vector(634-652) and times it (655-657), then says “Now let’s try with larger \(M\) to get a more accurate calculation” (664) and callscompute_call_price(M=10_000_000)- the Python-loop version from 584, at a thousand times the sample size. The one cell meant to show the payoff of vectorising times the unvectorised function. 685 and 696 break the second exercise in two directions: 679 states “an underlying with spot price of $100 and a knockout barrier of $120” while the solution setsdefault_S0 = 10(696) and puts 100 indefault_K(698), so the exercise’s spot price is the solution’s strike and the solution’s spot price appears nowhere in the exercise; and 685 tells the reader to “use the dynamics defined in{eq}`s_mc_dyms`”, which is the constant-volatility model at 444-448, while the solution at 725-726 simulates the stochastic-volatility model of 476-484. 497 is quieter but undercuts a whole section:default_ν = 0.001with \(h_0 = 0\) and \(\rho = 0.1\) confines \(h_t\) to about \([-0.001, 0.001]\), so \(\sigma_t = \exp(h_t)\) is constant to three decimal places and the “more realistic dynamics” that 458-486 spends thirty lines motivating is numerically the simple model it was introduced to replace - and nothing says so.[qe-writing-005] (reviewer) — Use bold for definitions, italic for emphasis. Count: 4. Lines: 135, 258, 349, 681. Example: the lecture bolds four terms - risk-neutral price (284), expiry date, strike price and underlying (353-355) - and leaves every other term it defines with no emphasis at all, including the four the lecture is named after or built on. 135 defines the subject of the lecture in plain text: “This is the Monte Carlo method, which runs as follows”. 258 defines risk-neutral pricing in plain text (“When we use risk-neutral pricing, we determine the price of a given asset according to its expected payoff”) even though the number it produces is bolded twenty-six lines later. 349 introduces the European call option, the object of two whole sections, unmarked. 681 defines the knockout in the exercise (“if the spot price ever moves above $120, the option ‘knocks out’ and the contract is null and void”) with quotation marks doing the work bold should do. There is also no italic emphasis anywhere in the 781 lines, so the file has no working distinction between the two forms - just four bolded terms and nothing else.
[qe-writing-007] (reviewer) — Use visual elements to enhance understanding. Count: 3. Lines: 93, 411, 704. Example: a lecture about the distribution of a random payoff draws exactly one figure (547-560), and it is the one figure that is not about a distribution. 83-103 turns entirely on the shape of the lognormal - 71-78 asks the reader to weigh a high mean against a high variance, and 93-103 gives both in closed form - with no density plot to make either concrete. 409-414 draws ten million payoffs into
return_drawsand reduces them to a single printed number; the histogram is one line away and would show the reader that the option price is the mean of a very skewed distribution, which is the whole reason Monte Carlo is being used. Andmonte_carlo_ex2(676-778) is about a path crossing a level: one simulated path withax.axhline(bp)would show a knockout happening, and the exercise instead relies on the reader believing thatoption_is_nullis doing what 767 says. The one figure that exists (547-560) has no axis labels, no legend and nolw, and it stacks 50 paths and 50 log paths where the point of comparing them is never stated in the prose.[qe-writing-008] — Remove excessive whitespace between words. Count: 2. Lines: 124, 368. Example: 2 spaces.
Low severity#
[qe-fig-005] — Descriptive figure names for cross-referencing. Count: 1. Lines: 547. Example: code-cell figure without mystnb figure metadata.
[qe-fig-008] — Use lw=2 for line charts. Count: 1. Lines: 555. Example: plot() without lw=.
Strengths#
The lecture is one question asked four times with the tool getting stronger at each pass - compute \(\mathbb E S\) when the distribution is known (83-103), when it is not (109-139), when the payoff is an option (347-414), and when the dynamics generate the distribution (418-611) - and line 105, “So far we have no need for a computer”, marks the exact point where the method becomes necessary rather than merely convenient.
Risk neutrality is introduced honestly rather than assumed. 283 concedes that “the definition of ‘fair’ is ambiguous”; 295-313 spends a whole subsection on the fact that the risk-neutral price ignores risk, invites the reader to notice they would probably not take the 50/50 bet (302-306), and then says plainly at 312 why the benchmark is still worth computing.
Discounting is derived before it is used: 321-325 makes the bank-account argument, 330-333 states the two rules, and 338-343 then applies them to the number already computed at 290 rather than folding \(\beta\) silently into a formula - so \(\beta^n \mathbb E G\) arrives as a consequence, not a definition.
Every speed claim in the lecture is measured. The pure-Python loop (172), the vectorized function (229) and the vectorized function at ten times the sample size (241) are each timed, so “Notice that this routine is much faster” (236) sits next to the numbers that justify it.
The barrier option is solved twice and the second solution explains itself: the loop version breaks out of the path as soon as the barrier is crossed (727-730), and the vectorized version carries the knockout forward in a boolean mask with the two comments a reader needs at 767 and 770 - “Mark all the options null where S_n > barrier price” and “mark payoff as 0 in the indices where options are null” - which is exactly the step that gets lost in a vectorized rewrite.
The proof that \(S_n\) is lognormal (440-455) is an induction in four one-sentence steps, taking the log substitution, the normality of \(s_1\), the extension to \(s_n\) and the exponentiation in turn - the right granularity for a first course, and it is what licenses the closed-form pricing at 377-385.
Probability events are written with braces after \(\mathbb P\) (273), satisfying the proposed qe-math-014 (proposed) convention.
Recommended actions#
Fix the exercise 1 solution at 664-668: it says “let’s try with larger \(M\)” and calls
compute_call_price(M=10_000_000), the Python-loop version, so the cell that is supposed to show what vectorising bought instead runs the slow function at a thousand times the sample size. It should callcompute_call_price_vector(M=10_000_000).Reconcile exercise 2 with its solution: 679 states a spot price of $100, the solution sets
default_S0 = 10(696) and uses 100 as the strike (698); and 685 points the reader at{eq}`s_mc_dyms`, the constant-volatility model of 444-448, while the solution simulates the stochastic-volatility model of 476-484 at 725-726.Say what
default_ν = 0.001(497) does, or raise it: with \(h_0 = 0\) and \(\rho = 0.1\) it pins \(\sigma_t = \exp(h_t)\) to within 0.3% of 1, so the “more realistic dynamics” of 472-486 is numerically the simple model of 428-455 and the section’s own motivation (458-469) is left unmet by its defaults.The
qe-writing-006finding at 52 is a false positive - “An introduction to Monte Carlo” is already correct sentence case, because “Monte Carlo” is a proper noun. Do not lowercase it (see scanner_doubts).Delete one of the two identical loops: 174-180 and the body of
compute_meanat 192-198 are the same six lines, andcompute_meanis called once at 206 and never used again. Keeping the function and dropping the bare loop also gives the section something to time againstcompute_mean_vectorized.Replace the eight timing magics with
qe.Timer()(qe-code-004: 172, 229, 241, 610, 656, 667, 741, 777) - note that 741 and 777 use the line form%time, so the fix differs slightly there.Add the two figures the argument needs: a density plot of the lognormal for the \(\mu, \sigma\) of 390-391 alongside 83-103, and a histogram of
return_draws(411) beside the printed price, so the reader sees the skewed payoff distribution the mean is taken over. In the barrier exercise, one path plusax.axhline(bp)would show a knockout.Fix the heading levels: “#### A routine using loops in python” (161) is an H4 inside “Share price with unknown distribution” (109), while its counterpart “### A vectorized routine” (211) is an H3 sibling of that whole section - so the pair the prose presents together at 215-217 sits at two different depths. Also capitalise “Python” in the 161 heading.
Bold the terms the lecture defines and leaves plain: “Monte Carlo method” (135), “risk-neutral pricing” (258), “European call option” (349) and the knockout at 681 - it bolds four terms (284, 353-355) and nothing else, and uses no italic emphasis anywhere.
Clean the code: the
):at 191, the aligned assignments at 495-499 and 693-697, the continuation indents at 526-531 and 635-643,return (S / n)at 198, and the namereturn_drawsat 411, which holds payoffs.Finish the figure and maths items: mystnb
caption/namemetadata on the figure cell at 547 (qe-fig-005),lw=2at 555 (qe-fig-008), the twoax.set_titlepanel labels at 556 moved into the caption (qe-fig-003 - and note a single caption cannot label two panels, so this needs a decision rather than a deletion),\operatorname{Var}or\mathbb Vat 101 to match\mathbb Eat 94, matched delimiters on 273,\mathrm{LN}at 87, 125 and 377, braces on the ten unbraced blackboard operators (qe-math-010 (proposed)), and the two double spaces at 124 and 368.Edit 464-467: “Instead it rather changes over time” needs rewriting, and “GFC” should be expanded on first use.