I'm trying to write a simplistic Monte Carlo simulator to predict asset prices. For example, if something has an initial value of 50 and an historic daily standard deviation of 2, what are the odds it will be 40, 40-50, 50-60 or greater than 60 in 25 days?

import random

start = 50
sd = 2
days = 25
bins = [40,50,60]

iterations = 1000
results = [0] * (len(bins) + 1)

GAUSS = True # distribution can be gaussian or lognormal

for _ in range(iterations):
    mc = start
    for _ in range(days):
        if GAUSS:
            mc += random.gauss(0, sd)
            if random.randint(0,1):
                mc += random.lognormvariate(0, sd)
                mc -= random.lognormvariate(0, sd)
        if mc <= 0: # prices can't go below 0
            mc = 0

    for n, bin in enumerate(bins): # bin the iteration
        if mc < bin:
            results[n] += 1
        results[-1] += 1

final = [100 * r / float(iterations) for r in results]

1 Answer 1


The values at the top of your script are constants so, per the style guide, should be UPPERCASE_WITH_UNDERSCORES:

BINS = [40, 50, 60]
DAYS = 25
GAUSS = True  # distribution can be gaussian or lognormal

Note that I've also made some of the names a little more meaningful, and added more Pythonic whitespace to the list. I've also ordered them alphabetically, although there may be a more sensible grouping.

It would be nice to add a comment explaining BINS - the rest are fairly obvious, but it took me a few reads to figure out that they're the top of each bin, and an additional higher bin will be automatically added. Consider adding a module docstring covering this.

Currently, all of the code is just in the body of the script. Instead (as it makes it easier to import and reuse this functionality elsewhere), it is conventional to define an "entry point" function (conventionally named main) and call that when the script is invoked directly:

# imports, constants, etc.

def main():  
    # do your thing

if __name__ == '__main__':

Rather than specify two distributions within the body of the code, I would extract the distribution function as a parameter to the simulation. In this case the functions would be:

def gaussian(std_dev):
    """Apply the Gaussian distribution."""
    return random.gauss(0, std_dev)

def lognormal(std_dev):
    """Apply the log-normal distribution."""
    return random.choice((1, -1)) * random.lognormvariate(0, std_dev)

Now if you wrote a function to do one iteration, it would be very simple:

def simulate(days, start_price, std_dev, distribution):
    """Simulate the asset for the specified number of days."""
    price = start_price
    for _ in range(days):
        price = max(0, price + distribution(std_dev))
    return price

You can then create more distribution functions in the future, and pass whichever you want to simulate; all it requires is a function that takes a single argument and returns a number.

Note that I have made the constants explicit parameters to these functions - this, again, makes reuse of the code easier. main would now look like:

def main():
    results = [0] * (len(BINS) + 1)
    dist = gaussian if GAUSS else lognormal
    for _ in range(ITERATIONS):
        final_price = simulate(DAYS, START_PRICE, STD_DEV, dist)
    print(final_results)  # you could return final_results instead

You could also abstract the binning of results into a function. I would avoid using the identifier bin, as this shadows a built-in function

  • \$\begingroup\$ Any thoughts on whether the substance of the algorithms makes sense? Or is that not an appropriate code review question? \$\endgroup\$
    – foosion
    Commented Apr 8, 2015 at 17:35
  • \$\begingroup\$ @foosion from a Monte Carlo POV they seem fine (I've written similar code for similar problems). I don't know much about the statistical distribution part, though, so will avoid making any comment there! \$\endgroup\$
    – jonrsharpe
    Commented Apr 8, 2015 at 17:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.