diff --git a/.gitignore b/.gitignore index 1c4e86d..e41947a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ build/* dist/* +doc/build/* *.egg-info __pycache__ .ruff_cache diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..be9d2e1 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,24 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +buildapi: + sphinx-apidoc -efM ../squigglepy -o source/reference + @echo "Auto-generation of API documentation finished. " \ + "The generated files are in 'api/'" diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..543c6b1 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..ebbe282 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = "Squigglepy" +copyright = "2023, Peter Wildeford" +author = "Peter Wildeford" + +# The short X.Y version +version = "" +# The full version, including alpha/beta/rc tags +release = "" + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.imgmath", + "sphinx.ext.viewcode", + "numpydoc", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "pydata_sphinx_theme" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "Squigglepydoc" + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, "Squigglepy.tex", "Squigglepy Documentation", "Peter Wildeford", "manual"), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "squigglepy", "Squigglepy Documentation", [author], 1)] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "Squigglepy", + "Squigglepy Documentation", + author, + "Squigglepy", + "One line description of project.", + "Miscellaneous", + ), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ["search.html"] + + +# -- Extension configuration ------------------------------------------------- + +numpydoc_class_members_toctree = False diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..3461183 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,51 @@ +Squigglepy: Implementation of Squiggle in Python +================================================ + +`Squiggle `__ is a "simple +programming language for intuitive probabilistic estimation". It serves +as its own standalone programming language with its own syntax, but it +is implemented in JavaScript. I like the features of Squiggle and intend +to use it frequently, but I also sometimes want to use similar +functionalities in Python, especially alongside other Python statistical +programming packages like Numpy, Pandas, and Matplotlib. The +**squigglepy** package here implements many Squiggle-like +functionalities in Python. + +.. toctree:: + :maxdepth: 2 + :caption: Contents + + Installation + Usage + API Reference + +Disclaimers +----------- + +This package is unofficial and supported by Peter Wildeford and Rethink +Priorities. It is not affiliated with or associated with the Quantified +Uncertainty Research Institute, which maintains the Squiggle language +(in JavaScript). + +This package is also new and not yet in a stable production version, so +you may encounter bugs and other errors. Please report those so they can +be fixed. It’s also possible that future versions of the package may +introduce breaking changes. + +This package is available under an MIT License. + +Acknowledgements +---------------- + +- The primary author of this package is Peter Wildeford. Agustín + Covarrubias and Bernardo Baron contributed several key features and + developments. +- Thanks to Ozzie Gooen and the Quantified Uncertainty Research + Institute for creating and maintaining the original Squiggle + language. +- Thanks to Dawn Drescher for helping me implement math between + distributions. +- Thanks to Dawn Drescher for coming up with the idea to use ``~`` as a + shorthand for ``sample``, as well as helping me implement it. + +.. autosummary:: diff --git a/doc/source/installation.rst b/doc/source/installation.rst new file mode 100644 index 0000000..fb5e8d9 --- /dev/null +++ b/doc/source/installation.rst @@ -0,0 +1,12 @@ +Installation +============ + +.. code:: shell + + pip install squigglepy + +For plotting support, you can also use the ``plots`` extra: + +.. code:: shell + + pip install squigglepy[plots] diff --git a/doc/source/reference/modules.rst b/doc/source/reference/modules.rst new file mode 100644 index 0000000..57192bd --- /dev/null +++ b/doc/source/reference/modules.rst @@ -0,0 +1,7 @@ +squigglepy +========== + +.. toctree:: + :maxdepth: 4 + + squigglepy diff --git a/doc/source/reference/squigglepy.bayes.rst b/doc/source/reference/squigglepy.bayes.rst new file mode 100644 index 0000000..8a445be --- /dev/null +++ b/doc/source/reference/squigglepy.bayes.rst @@ -0,0 +1,7 @@ +squigglepy.bayes module +======================= + +.. automodule:: squigglepy.bayes + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/reference/squigglepy.correlation.rst b/doc/source/reference/squigglepy.correlation.rst new file mode 100644 index 0000000..c99cf14 --- /dev/null +++ b/doc/source/reference/squigglepy.correlation.rst @@ -0,0 +1,7 @@ +squigglepy.correlation module +============================= + +.. automodule:: squigglepy.correlation + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/reference/squigglepy.distributions.rst b/doc/source/reference/squigglepy.distributions.rst new file mode 100644 index 0000000..bfbdb38 --- /dev/null +++ b/doc/source/reference/squigglepy.distributions.rst @@ -0,0 +1,7 @@ +squigglepy.distributions module +=============================== + +.. automodule:: squigglepy.distributions + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/reference/squigglepy.numbers.rst b/doc/source/reference/squigglepy.numbers.rst new file mode 100644 index 0000000..524cbcd --- /dev/null +++ b/doc/source/reference/squigglepy.numbers.rst @@ -0,0 +1,7 @@ +squigglepy.numbers module +========================= + +.. automodule:: squigglepy.numbers + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/reference/squigglepy.rng.rst b/doc/source/reference/squigglepy.rng.rst new file mode 100644 index 0000000..84ec740 --- /dev/null +++ b/doc/source/reference/squigglepy.rng.rst @@ -0,0 +1,7 @@ +squigglepy.rng module +===================== + +.. automodule:: squigglepy.rng + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/reference/squigglepy.rst b/doc/source/reference/squigglepy.rst new file mode 100644 index 0000000..b629fda --- /dev/null +++ b/doc/source/reference/squigglepy.rst @@ -0,0 +1,22 @@ +squigglepy package +================== + +.. automodule:: squigglepy + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + squigglepy.bayes + squigglepy.correlation + squigglepy.distributions + squigglepy.numbers + squigglepy.rng + squigglepy.samplers + squigglepy.utils + squigglepy.version diff --git a/doc/source/reference/squigglepy.samplers.rst b/doc/source/reference/squigglepy.samplers.rst new file mode 100644 index 0000000..ae3e754 --- /dev/null +++ b/doc/source/reference/squigglepy.samplers.rst @@ -0,0 +1,7 @@ +squigglepy.samplers module +========================== + +.. automodule:: squigglepy.samplers + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/reference/squigglepy.utils.rst b/doc/source/reference/squigglepy.utils.rst new file mode 100644 index 0000000..8af5f44 --- /dev/null +++ b/doc/source/reference/squigglepy.utils.rst @@ -0,0 +1,7 @@ +squigglepy.utils module +======================= + +.. automodule:: squigglepy.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/reference/squigglepy.version.rst b/doc/source/reference/squigglepy.version.rst new file mode 100644 index 0000000..efe11f5 --- /dev/null +++ b/doc/source/reference/squigglepy.version.rst @@ -0,0 +1,7 @@ +squigglepy.version module +========================= + +.. automodule:: squigglepy.version + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/usage.rst b/doc/source/usage.rst new file mode 100644 index 0000000..3e03a70 --- /dev/null +++ b/doc/source/usage.rst @@ -0,0 +1,476 @@ +Examples +======== + +Piano tuners example +~~~~~~~~~~~~~~~~~~~~ + +Here’s the Squigglepy implementation of `the example from Squiggle +Docs `__: + +.. code:: python + + import squigglepy as sq + import numpy as np + import matplotlib.pyplot as plt + from squigglepy.numbers import K, M + from pprint import pprint + + pop_of_ny_2022 = sq.to(8.1*M, 8.4*M) # This means that you're 90% confident the value is between 8.1 and 8.4 Million. + pct_of_pop_w_pianos = sq.to(0.2, 1) * 0.01 # We assume there are almost no people with multiple pianos + pianos_per_piano_tuner = sq.to(2*K, 50*K) + piano_tuners_per_piano = 1 / pianos_per_piano_tuner + total_tuners_in_2022 = pop_of_ny_2022 * pct_of_pop_w_pianos * piano_tuners_per_piano + samples = total_tuners_in_2022 @ 1000 # Note: `@ 1000` is shorthand to get 1000 samples + + # Get mean and SD + print('Mean: {}, SD: {}'.format(round(np.mean(samples), 2), + round(np.std(samples), 2))) + + # Get percentiles + pprint(sq.get_percentiles(samples, digits=0)) + + # Histogram + plt.hist(samples, bins=200) + plt.show() + + # Shorter histogram + total_tuners_in_2022.plot() + +And the version from the Squiggle doc that incorporates time: + +.. code:: python + + import squigglepy as sq + from squigglepy.numbers import K, M + + pop_of_ny_2022 = sq.to(8.1*M, 8.4*M) + pct_of_pop_w_pianos = sq.to(0.2, 1) * 0.01 + pianos_per_piano_tuner = sq.to(2*K, 50*K) + piano_tuners_per_piano = 1 / pianos_per_piano_tuner + + def pop_at_time(t): # t = Time in years after 2022 + avg_yearly_pct_change = sq.to(-0.01, 0.05) # We're expecting NYC to continuously grow with an mean of roughly between -1% and +4% per year + return pop_of_ny_2022 * ((avg_yearly_pct_change + 1) ** t) + + def total_tuners_at_time(t): + return pop_at_time(t) * pct_of_pop_w_pianos * piano_tuners_per_piano + + # Get total piano tuners at 2030 + sq.get_percentiles(total_tuners_at_time(2030-2022) @ 1000) + +**WARNING:** Be careful about dividing by ``K``, ``M``, etc. ``1/2*K`` = +500 in Python. Use ``1/(2*K)`` instead to get the expected outcome. + +**WARNING:** Be careful about using ``K`` to get sample counts. Use +``sq.norm(2, 3) @ (2*K)``\ … ``sq.norm(2, 3) @ 2*K`` will return only +two samples, multiplied by 1000. + +Distributions +~~~~~~~~~~~~~ + +.. code:: python + + import squigglepy as sq + + # Normal distribution + sq.norm(1, 3) # 90% interval from 1 to 3 + + # Distribution can be sampled with mean and sd too + sq.norm(mean=0, sd=1) + + # Shorthand to get one sample + ~sq.norm(1, 3) + + # Shorthand to get more than one sample + sq.norm(1, 3) @ 100 + + # Longhand version to get more than one sample + sq.sample(sq.norm(1, 3), n=100) + + # Nice progress reporter + sq.sample(sq.norm(1, 3), n=1000, verbose=True) + + # Other distributions exist + sq.lognorm(1, 10) + sq.tdist(1, 10, t=5) + sq.triangular(1, 2, 3) + sq.pert(1, 2, 3, lam=2) + sq.binomial(p=0.5, n=5) + sq.beta(a=1, b=2) + sq.bernoulli(p=0.5) + sq.poisson(10) + sq.chisquare(2) + sq.gamma(3, 2) + sq.pareto(1) + sq.exponential(scale=1) + sq.geometric(p=0.5) + + # Discrete sampling + sq.discrete({'A': 0.1, 'B': 0.9}) + + # Can return integers + sq.discrete({0: 0.1, 1: 0.3, 2: 0.3, 3: 0.15, 4: 0.15}) + + # Alternate format (also can be used to return more complex objects) + sq.discrete([[0.1, 0], + [0.3, 1], + [0.3, 2], + [0.15, 3], + [0.15, 4]]) + + sq.discrete([0, 1, 2]) # No weights assumes equal weights + + # You can mix distributions together + sq.mixture([sq.norm(1, 3), + sq.norm(4, 10), + sq.lognorm(1, 10)], # Distributions to mix + [0.3, 0.3, 0.4]) # These are the weights on each distribution + + # This is equivalent to the above, just a different way of doing the notation + sq.mixture([[0.3, sq.norm(1,3)], + [0.3, sq.norm(4,10)], + [0.4, sq.lognorm(1,10)]]) + + # Make a zero-inflated distribution + # 60% chance of returning 0, 40% chance of sampling from `norm(1, 2)`. + sq.zero_inflated(0.6, sq.norm(1, 2)) + +Additional features +~~~~~~~~~~~~~~~~~~~ + +.. code:: python + + import squigglepy as sq + + # You can add and subtract distributions + (sq.norm(1,3) + sq.norm(4,5)) @ 100 + (sq.norm(1,3) - sq.norm(4,5)) @ 100 + (sq.norm(1,3) * sq.norm(4,5)) @ 100 + (sq.norm(1,3) / sq.norm(4,5)) @ 100 + + # You can also do math with numbers + ~((sq.norm(sd=5) + 2) * 2) + ~(-sq.lognorm(0.1, 1) * sq.pareto(1) / 10) + + # You can change the CI from 90% (default) to 80% + sq.norm(1, 3, credibility=80) + + # You can clip + sq.norm(0, 3, lclip=0, rclip=5) # Sample norm with a 90% CI from 0-3, but anything lower than 0 gets clipped to 0 and anything higher than 5 gets clipped to 5. + + # You can also clip with a function, and use pipes + sq.norm(0, 3) >> sq.clip(0, 5) + + # You can correlate continuous distributions + a, b = sq.uniform(-1, 1), sq.to(0, 3) + a, b = sq.correlate((a, b), 0.5) # Correlate a and b with a correlation of 0.5 + # You can even pass your own correlation matrix! + a, b = sq.correlate((a, b), [[1, 0.5], [0.5, 1]]) + +Example: Rolling a die +^^^^^^^^^^^^^^^^^^^^^^ + +An example of how to use distributions to build tools: + +.. code:: python + + import squigglepy as sq + + def roll_die(sides, n=1): + return sq.discrete(list(range(1, sides + 1))) @ n if sides > 0 else None + + roll_die(sides=6, n=10) + # [2, 6, 5, 2, 6, 2, 3, 1, 5, 2] + +This is already included standard in the utils of this package. Use +``sq.roll_die``. + +Bayesian inference +~~~~~~~~~~~~~~~~~~ + +1% of women at age forty who participate in routine screening have +breast cancer. 80% of women with breast cancer will get positive +mammographies. 9.6% of women without breast cancer will also get +positive mammographies. + +A woman in this age group had a positive mammography in a routine +screening. What is the probability that she actually has breast cancer? + +We can approximate the answer with a Bayesian network (uses rejection +sampling): + +.. code:: python + + import squigglepy as sq + from squigglepy import bayes + from squigglepy.numbers import M + + def mammography(has_cancer): + return sq.event(0.8 if has_cancer else 0.096) + + def define_event(): + cancer = ~sq.bernoulli(0.01) + return({'mammography': mammography(cancer), + 'cancer': cancer}) + + bayes.bayesnet(define_event, + find=lambda e: e['cancer'], + conditional_on=lambda e: e['mammography'], + n=1*M) + # 0.07723995880535531 + +Or if we have the information immediately on hand, we can directly +calculate it. Though this doesn’t work for very complex stuff. + +.. code:: python + + from squigglepy import bayes + bayes.simple_bayes(prior=0.01, likelihood_h=0.8, likelihood_not_h=0.096) + # 0.07763975155279504 + +You can also make distributions and update them: + +.. code:: python + + import matplotlib.pyplot as plt + import squigglepy as sq + from squigglepy import bayes + from squigglepy.numbers import K + import numpy as np + + print('Prior') + prior = sq.norm(1,5) + prior_samples = prior @ (10*K) + plt.hist(prior_samples, bins = 200) + plt.show() + print(sq.get_percentiles(prior_samples)) + print('Prior Mean: {} SD: {}'.format(np.mean(prior_samples), np.std(prior_samples))) + print('-') + + print('Evidence') + evidence = sq.norm(2,3) + evidence_samples = evidence @ (10*K) + plt.hist(evidence_samples, bins = 200) + plt.show() + print(sq.get_percentiles(evidence_samples)) + print('Evidence Mean: {} SD: {}'.format(np.mean(evidence_samples), np.std(evidence_samples))) + print('-') + + print('Posterior') + posterior = bayes.update(prior, evidence) + posterior_samples = posterior @ (10*K) + plt.hist(posterior_samples, bins = 200) + plt.show() + print(sq.get_percentiles(posterior_samples)) + print('Posterior Mean: {} SD: {}'.format(np.mean(posterior_samples), np.std(posterior_samples))) + + print('Average') + average = bayes.average(prior, evidence) + average_samples = average @ (10*K) + plt.hist(average_samples, bins = 200) + plt.show() + print(sq.get_percentiles(average_samples)) + print('Average Mean: {} SD: {}'.format(np.mean(average_samples), np.std(average_samples))) + +Example: Alarm net +^^^^^^^^^^^^^^^^^^ + +This is the alarm network from `Bayesian Artificial Intelligence - +Section +2.5.1 `__: + + Assume your house has an alarm system against burglary. + + You live in the seismically active area and the alarm system can get + occasionally set off by an earthquake. + + You have two neighbors, Mary and John, who do not know each other. If + they hear the alarm they call you, but this is not guaranteed. + + The chance of a burglary on a particular day is 0.1%. The chance of + an earthquake on a particular day is 0.2%. + + The alarm will go off 95% of the time with both a burglary and an + earthquake, 94% of the time with just a burglary, 29% of the time + with just an earthquake, and 0.1% of the time with nothing (total + false alarm). + + John will call you 90% of the time when the alarm goes off. But on 5% + of the days, John will just call to say “hi”. Mary will call you 70% + of the time when the alarm goes off. But on 1% of the days, Mary will + just call to say “hi”. + +.. code:: python + + import squigglepy as sq + from squigglepy import bayes + from squigglepy.numbers import M + + def p_alarm_goes_off(burglary, earthquake): + if burglary and earthquake: + return 0.95 + elif burglary and not earthquake: + return 0.94 + elif not burglary and earthquake: + return 0.29 + elif not burglary and not earthquake: + return 0.001 + + def p_john_calls(alarm_goes_off): + return 0.9 if alarm_goes_off else 0.05 + + def p_mary_calls(alarm_goes_off): + return 0.7 if alarm_goes_off else 0.01 + + def define_event(): + burglary_happens = sq.event(p=0.001) + earthquake_happens = sq.event(p=0.002) + alarm_goes_off = sq.event(p_alarm_goes_off(burglary_happens, earthquake_happens)) + john_calls = sq.event(p_john_calls(alarm_goes_off)) + mary_calls = sq.event(p_mary_calls(alarm_goes_off)) + return {'burglary': burglary_happens, + 'earthquake': earthquake_happens, + 'alarm_goes_off': alarm_goes_off, + 'john_calls': john_calls, + 'mary_calls': mary_calls} + + # What are the chances that both John and Mary call if an earthquake happens? + bayes.bayesnet(define_event, + n=1*M, + find=lambda e: (e['mary_calls'] and e['john_calls']), + conditional_on=lambda e: e['earthquake']) + # Result will be ~0.19, though it varies because it is based on a random sample. + # This also may take a minute to run. + + # If both John and Mary call, what is the chance there's been a burglary? + bayes.bayesnet(define_event, + n=1*M, + find=lambda e: e['burglary'], + conditional_on=lambda e: (e['mary_calls'] and e['john_calls'])) + # Result will be ~0.27, though it varies because it is based on a random sample. + # This will run quickly because there is a built-in cache. + # Use `cache=False` to not build a cache and `reload_cache=True` to recalculate the cache. + +Note that the amount of Bayesian analysis that squigglepy can do is +pretty limited. For more complex bayesian analysis, consider +`sorobn `__, +`pomegranate `__, +`bnlearn `__, or +`pyMC `__. + +Example: A demonstration of the Monty Hall Problem +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + import squigglepy as sq + from squigglepy import bayes + from squigglepy.numbers import K, M, B, T + + + def monte_hall(door_picked, switch=False): + doors = ['A', 'B', 'C'] + car_is_behind_door = ~sq.discrete(doors) + reveal_door = ~sq.discrete([d for d in doors if d != door_picked and d != car_is_behind_door]) + + if switch: + old_door_picked = door_picked + door_picked = [d for d in doors if d != old_door_picked and d != reveal_door][0] + + won_car = (car_is_behind_door == door_picked) + return won_car + + + def define_event(): + door = ~sq.discrete(['A', 'B', 'C']) + switch = sq.event(0.5) + return {'won': monte_hall(door_picked=door, switch=switch), + 'switched': switch} + + RUNS = 10*K + r = bayes.bayesnet(define_event, + find=lambda e: e['won'], + conditional_on=lambda e: e['switched'], + verbose=True, + n=RUNS) + print('Win {}% of the time when switching'.format(int(r * 100))) + + r = bayes.bayesnet(define_event, + find=lambda e: e['won'], + conditional_on=lambda e: not e['switched'], + verbose=True, + n=RUNS) + print('Win {}% of the time when not switching'.format(int(r * 100))) + + # Win 66% of the time when switching + # Win 34% of the time when not switching + +Example: More complex coin/dice interactions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Imagine that I flip a coin. If heads, I take a random die out of my + blue bag. If tails, I take a random die out of my red bag. The blue + bag contains only 6-sided dice. The red bag contains a 4-sided die, a + 6-sided die, a 10-sided die, and a 20-sided die. I then roll the + random die I took. What is the chance that I roll a 6? + +.. code:: python + + import squigglepy as sq + from squigglepy.numbers import K, M, B, T + from squigglepy import bayes + + def define_event(): + if sq.flip_coin() == 'heads': # Blue bag + return sq.roll_die(6) + else: # Red bag + return sq.discrete([4, 6, 10, 20]) >> sq.roll_die + + + bayes.bayesnet(define_event, + find=lambda e: e == 6, + verbose=True, + n=100*K) + # This run for me returned 0.12306 which is pretty close to the correct answer of 0.12292 + +Kelly betting +~~~~~~~~~~~~~ + +You can use probability generated, combine with a bankroll to determine +bet sizing using `Kelly +criterion `__. + +For example, if you want to Kelly bet and you’ve… + +- determined that your price (your probability of the event in question + happening / the market in question resolving in your favor) is $0.70 + (70%) +- see that the market is pricing at $0.65 +- you have a bankroll of $1000 that you are willing to bet + +You should bet as follows: + +.. code:: python + + import squigglepy as sq + kelly_data = sq.kelly(my_price=0.70, market_price=0.65, bankroll=1000) + kelly_data['kelly'] # What fraction of my bankroll should I bet on this? + # 0.143 + kelly_data['target'] # How much money should be invested in this? + # 142.86 + kelly_data['expected_roi'] # What is the expected ROI of this bet? + # 0.077 + +More examples +~~~~~~~~~~~~~ + +You can see more examples of squigglepy in action +`here `__. + +Run tests +--------- + +Use ``black .`` for formatting. + +Run +``ruff check . && pytest && pip3 install . && python3 tests/integration.py`` diff --git a/poetry.lock b/poetry.lock index 86c4e93..42f9236 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,30 @@ # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +[[package]] +name = "accessible-pygments" +version = "0.0.4" +description = "A collection of accessible pygments styles" +optional = false +python-versions = "*" +files = [ + {file = "accessible-pygments-0.0.4.tar.gz", hash = "sha256:e7b57a9b15958e9601c7e9eb07a440c813283545a20973f2574a5f453d0e953e"}, + {file = "accessible_pygments-0.0.4-py2.py3-none-any.whl", hash = "sha256:416c6d8c1ea1c5ad8701903a20fcedf953c6e720d64f33dc47bfb2d3f2fa4e8d"}, +] + +[package.dependencies] +pygments = ">=1.5" + +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + [[package]] name = "ansi2html" version = "1.8.0" @@ -33,6 +58,38 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "babel" +version = "2.13.1" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, + {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "black" version = "23.3.0" @@ -438,6 +495,17 @@ files = [ [package.extras] graph = ["objgraph (>=1.7.2)"] +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + [[package]] name = "exceptiongroup" version = "1.1.1" @@ -602,6 +670,17 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + [[package]] name = "importlib-metadata" version = "6.7.0" @@ -1369,6 +1448,33 @@ files = [ [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +[[package]] +name = "pydata-sphinx-theme" +version = "0.14.3" +description = "Bootstrap-based Sphinx theme from the PyData community" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydata_sphinx_theme-0.14.3-py3-none-any.whl", hash = "sha256:b7e40cd75a20449adfe2d7525be379b9fe92f6d31e5233e449fa34ddcd4398d9"}, + {file = "pydata_sphinx_theme-0.14.3.tar.gz", hash = "sha256:bd474f347895f3fc5b6ce87390af64330ee54f11ebf9660d5bc3f87d532d4e5c"}, +] + +[package.dependencies] +accessible-pygments = "*" +Babel = "*" +beautifulsoup4 = "*" +docutils = "!=0.17.0" +packaging = "*" +pygments = ">=2.7" +sphinx = ">=5.0" +typing-extensions = "*" + +[package.extras] +a11y = ["pytest-playwright"] +dev = ["nox", "pre-commit", "pydata-sphinx-theme[doc,test]", "pyyaml"] +doc = ["ablog (>=0.11.0rc2)", "colorama", "ipykernel", "ipyleaflet", "jupyter_sphinx", "jupyterlite-sphinx", "linkify-it-py", "matplotlib", "myst-parser", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "rich", "sphinx-autoapi (>=3.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-favicon (>=1.0.1)", "sphinx-sitemap", "sphinx-togglebutton", "sphinxcontrib-youtube (<1.4)", "sphinxext-rediraffe", "xarray"] +test = ["pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "pygments" version = "2.15.1" @@ -1659,6 +1765,17 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + [[package]] name = "sortedcontainers" version = "2.4.0" @@ -1670,6 +1787,156 @@ files = [ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + +[[package]] +name = "sphinx" +version = "7.2.6" +description = "Python documentation generator" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, + {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.21" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.14" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.7" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"}, + {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.5" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"}, + {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.4" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"}, + {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.6" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"}, + {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.9" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"}, + {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + [[package]] name = "tenacity" version = "8.2.2" @@ -1809,4 +2076,4 @@ plots = ["matplotlib"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "5625f6f3eec6de2121f93d9cb0591c23402a9c232f824ce8353c50b7441e3a9d" +content-hash = "f9f1beabf7d339e2173f854e8559f6bd9f1fbb28d711802c3d0d831a7b1fa2b5" diff --git a/pyproject.toml b/pyproject.toml index d4e93d2..b35de76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ pathos = "^0.3.0" msgspec = "^0.15.1" matplotlib = { version = "^3.7.1", optional = true } pandas = { version = "^2.0.2", optional = true } +pydata-sphinx-theme = "^0.14.3" [tool.poetry.group.dev.dependencies] diff --git a/squigglepy/bayes.py b/squigglepy/bayes.py index 415f29a..e3fec17 100644 --- a/squigglepy/bayes.py +++ b/squigglepy/bayes.py @@ -1,3 +1,7 @@ +""" +This modules includes functions for Bayesian inference. +""" + import os import time import math diff --git a/squigglepy/correlation.py b/squigglepy/correlation.py index abd20b2..0108940 100644 --- a/squigglepy/correlation.py +++ b/squigglepy/correlation.py @@ -107,9 +107,10 @@ def correlate( >>> solar_radiation, temperature = sq.gamma(300, 100), sq.to(22, 28) >>> solar_radiation, temperature = sq.correlate((solar_radiation, temperature), 0.7) >>> print(np.corrcoef(solar_radiation @ 1000, temperature @ 1000)[0, 1]) - 0.6975960649767123 + 0.6975960649767123 Or you could pass a correlation matrix: + >>> funding_gap, cost_per_delivery, effect_size = ( sq.to(20_000, 80_000), sq.to(30, 80), sq.beta(2, 5) ) @@ -118,9 +119,9 @@ def correlate( [[1, 0.6, -0.5], [0.6, 1, -0.2], [-0.5, -0.2, 1]] ) >>> print(np.corrcoef(funding_gap @ 1000, cost_per_delivery @ 1000, effect_size @ 1000)) - array([[ 1. , 0.580520 , -0.480149], - [ 0.580962, 1. , -0.187831], - [-0.480149, -0.187831 , 1. ]]) + array([[ 1. , 0.580520 , -0.480149], + [ 0.580962, 1. , -0.187831], + [-0.480149, -0.187831 , 1. ]]) """ if not isinstance(variables, tuple): diff --git a/squigglepy/distributions.py b/squigglepy/distributions.py index 413e749..7b01f7a 100644 --- a/squigglepy/distributions.py +++ b/squigglepy/distributions.py @@ -1,3 +1,7 @@ +""" +A collection of probability distributions and functions to operate on them. +""" + import operator import math import numpy as np diff --git a/squigglepy/utils.py b/squigglepy/utils.py index 03e02dc..fa6d9e2 100644 --- a/squigglepy/utils.py +++ b/squigglepy/utils.py @@ -912,25 +912,25 @@ def kelly(my_price, market_price, deference=0, bankroll=1, resolve_date=None, cu ------- dict A dict of values specifying: - * ``my_price`` - * ``market_price`` - * ``deference`` - * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken - into account. - * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``. - * ``adj_delta_price`` : the absolute difference between ``adj_price`` and - ``market_price``. - * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll`` - you should bet. - * ``target`` : the target amount of money you should have invested - * ``current`` - * ``delta`` : the amount of money you should invest given what you already - have invested - * ``max_gain`` : the amount of money you would gain if you win - * ``modeled_gain`` : the expected value you would win given ``adj_price`` - * ``expected_roi`` : the expected return on investment - * ``expected_arr`` : the expected ARR given ``resolve_date`` - * ``resolve_date`` + * ``my_price`` + * ``market_price`` + * ``deference`` + * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken + into account. + * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``. + * ``adj_delta_price`` : the absolute difference between ``adj_price`` and + ``market_price``. + * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll`` + you should bet. + * ``target`` : the target amount of money you should have invested + * ``current`` + * ``delta`` : the amount of money you should invest given what you already + have invested + * ``max_gain`` : the amount of money you would gain if you win + * ``modeled_gain`` : the expected value you would win given ``adj_price`` + * ``expected_roi`` : the expected return on investment + * ``expected_arr`` : the expected ARR given ``resolve_date`` + * ``resolve_date`` Examples -------- @@ -1000,25 +1000,25 @@ def full_kelly(my_price, market_price, bankroll=1, resolve_date=None, current=0) ------- dict A dict of values specifying: - * ``my_price`` - * ``market_price`` - * ``deference`` - * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken - into account. - * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``. - * ``adj_delta_price`` : the absolute difference between ``adj_price`` and - ``market_price``. - * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll`` - you should bet. - * ``target`` : the target amount of money you should have invested - * ``current`` - * ``delta`` : the amount of money you should invest given what you already - have invested - * ``max_gain`` : the amount of money you would gain if you win - * ``modeled_gain`` : the expected value you would win given ``adj_price`` - * ``expected_roi`` : the expected return on investment - * ``expected_arr`` : the expected ARR given ``resolve_date`` - * ``resolve_date`` + * ``my_price`` + * ``market_price`` + * ``deference`` + * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken + into account. + * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``. + * ``adj_delta_price`` : the absolute difference between ``adj_price`` and + ``market_price``. + * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll`` + you should bet. + * ``target`` : the target amount of money you should have invested + * ``current`` + * ``delta`` : the amount of money you should invest given what you already + have invested + * ``max_gain`` : the amount of money you would gain if you win + * ``modeled_gain`` : the expected value you would win given ``adj_price`` + * ``expected_roi`` : the expected return on investment + * ``expected_arr`` : the expected ARR given ``resolve_date`` + * ``resolve_date`` Examples -------- @@ -1062,25 +1062,25 @@ def half_kelly(my_price, market_price, bankroll=1, resolve_date=None, current=0) ------- dict A dict of values specifying: - * ``my_price`` - * ``market_price`` - * ``deference`` - * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken - into account. - * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``. - * ``adj_delta_price`` : the absolute difference between ``adj_price`` and - ``market_price``. - * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll`` - you should bet. - * ``target`` : the target amount of money you should have invested - * ``current`` - * ``delta`` : the amount of money you should invest given what you already - have invested - * ``max_gain`` : the amount of money you would gain if you win - * ``modeled_gain`` : the expected value you would win given ``adj_price`` - * ``expected_roi`` : the expected return on investment - * ``expected_arr`` : the expected ARR given ``resolve_date`` - * ``resolve_date`` + * ``my_price`` + * ``market_price`` + * ``deference`` + * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken + into account. + * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``. + * ``adj_delta_price`` : the absolute difference between ``adj_price`` and + ``market_price``. + * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll`` + you should bet. + * ``target`` : the target amount of money you should have invested + * ``current`` + * ``delta`` : the amount of money you should invest given what you already + have invested + * ``max_gain`` : the amount of money you would gain if you win + * ``modeled_gain`` : the expected value you would win given ``adj_price`` + * ``expected_roi`` : the expected return on investment + * ``expected_arr`` : the expected ARR given ``resolve_date`` + * ``resolve_date`` Examples -------- @@ -1124,25 +1124,25 @@ def quarter_kelly(my_price, market_price, bankroll=1, resolve_date=None, current ------- dict A dict of values specifying: - * ``my_price`` - * ``market_price`` - * ``deference`` - * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken - into account. - * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``. - * ``adj_delta_price`` : the absolute difference between ``adj_price`` and - ``market_price``. - * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll`` - you should bet. - * ``target`` : the target amount of money you should have invested - * ``current`` - * ``delta`` : the amount of money you should invest given what you already - have invested - * ``max_gain`` : the amount of money you would gain if you win - * ``modeled_gain`` : the expected value you would win given ``adj_price`` - * ``expected_roi`` : the expected return on investment - * ``expected_arr`` : the expected ARR given ``resolve_date`` - * ``resolve_date`` + * ``my_price`` + * ``market_price`` + * ``deference`` + * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken + into account. + * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``. + * ``adj_delta_price`` : the absolute difference between ``adj_price`` and + ``market_price``. + * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll`` + you should bet. + * ``target`` : the target amount of money you should have invested + * ``current`` + * ``delta`` : the amount of money you should invest given what you already + have invested + * ``max_gain`` : the amount of money you would gain if you win + * ``modeled_gain`` : the expected value you would win given ``adj_price`` + * ``expected_roi`` : the expected return on investment + * ``expected_arr`` : the expected ARR given ``resolve_date`` + * ``resolve_date`` Examples --------