{"id":13500179,"url":"https://github.com/johnbywater/quantdsl","last_synced_at":"2025-04-05T14:04:30.294Z","repository":{"id":43961716,"uuid":"23003282","full_name":"johnbywater/quantdsl","owner":"johnbywater","description":"Quant DSL","archived":false,"fork":false,"pushed_at":"2018-04-14T19:44:54.000Z","size":1423,"stargazers_count":355,"open_issues_count":4,"forks_count":66,"subscribers_count":27,"default_branch":"master","last_synced_at":"2025-03-29T13:05:34.552Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnbywater.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-08-15T21:22:38.000Z","updated_at":"2025-03-28T09:41:14.000Z","dependencies_parsed_at":"2022-08-27T21:01:52.791Z","dependency_job_id":null,"html_url":"https://github.com/johnbywater/quantdsl","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnbywater%2Fquantdsl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnbywater%2Fquantdsl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnbywater%2Fquantdsl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnbywater%2Fquantdsl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnbywater","download_url":"https://codeload.github.com/johnbywater/quantdsl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247345850,"owners_count":20924102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T22:00:52.447Z","updated_at":"2025-04-05T14:04:30.266Z","avatar_url":"https://github.com/johnbywater.png","language":"Python","funding_links":[],"categories":["Python","repos:"],"sub_categories":["Numerical Libraries \u0026 Data Structures","数值库与数据结构"],"readme":"# Quant DSL\n\n***Domain specific language for quantitative analytics in finance and trading.***\n\n[![Build Status](https://secure.travis-ci.org/johnbywater/quantdsl.png)](https://travis-ci.org/johnbywater/quantdsl)\n[![Coverage Status](https://coveralls.io/repos/github/johnbywater/quantdsl/badge.svg)](https://coveralls.io/github/johnbywater/quantdsl)\n\nExample of an American option expressed in Quant DSL. \n\n```python skip-test\nAmericanOption(Date('2011-1-1'), Date('2012-1-1'), Market('Copper'), 7000, TimeDelta('1d'))\n\ndef AmericanOption(start, expiry, underlying, strike, step):\n    if start \u003c= expiry:\n        Option(start, underlying, strike, AmericanOption(\n            start + step, expiry, underlying, strike, step\n        ))\n    else:\n        0\n\ndef Option(expiry, underlying, strike, alternative):\n    Wait(expiry, Choice(underlying - strike, alternative))\n```\n\n## Install\n\nUse pip to install the [latest distribution](https://pypi.python.org/pypi/quantdsl) from\nthe Python Package Index. You may feedback [issues on GitHub](https://github.com/johnbywater/quantdsl/issues).\n\n```\npip install quantdsl\n```\n\nPlease note, this library depends on SciPy, which can fail to install with some older versions of pip. In case of \ndifficulty, please try again after upgrading pip.\n\n```\npip install --upgrade pip\n```\n\nAfter successfully installing this library, the test suite should pass.\n\n```\npython -m unittest discover quantdsl\n```\n\n\n## Overview\n\nQuant DSL is domain specific language for quantitative analytics in finance and trading.\n\nAt the heart of Quant DSL is a set of elements (e.g. \"Settlement\", \"Fixing\", \"Market\", \"Choice\").\nThe elements involve mathematical expressions commonly used within quantitative analytics, \nsuch as: \n[present value discounting](https://en.wikipedia.org/wiki/Present_value);\n[geometric Brownian motion](https://en.wikipedia.org/wiki/Geometric_Brownian_motion); and \n[least squares Monte Carlo](https://en.wikipedia.org/wiki/Monte_Carlo_methods_for_option_pricing#Least_Square_Monte_Carlo).\n\nThe elements of the language can be freely composed into expressions of value. The validity of\nMonte Carlo simulation for all possible expressions in the language is\n[proven by induction](http://www.appropriatesoftware.org/quant/docs/quant-dsl-definition-and-proof.pdf).\n\n\n### Syntax\n\nThe syntax of Quant DSL expressions is defined with\n[Backus–Naur Form](https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form).\n\n```\n\u003cExpression\u003e ::= \u003cConstant\u003e\n    | \"Settlement(\" \u003cDate\u003e \",\" \u003cExpression\u003e \")\"\n    | \"Fixing(\" \u003cDate\u003e \",\" \u003cExpression\u003e \")\"\n    | \"Market(\" \u003cMarketName\u003e \")\"\n    | \"Wait(\" \u003cDate\u003e \",\" \u003cExpression\u003e \")\"\n    | \"Choice(\" \u003cExpression\u003e \",\" \u003cExpression\u003e \")\"\n    | \"Max(\" \u003cExpression\u003e \",\" \u003cExpression\u003e \")\"\n    | \u003cExpression\u003e \"+\" \u003cExpression\u003e\n    | \u003cExpression\u003e \"-\" \u003cExpression\u003e\n    | \u003cExpression\u003e \"*\" \u003cExpression\u003e\n    | \u003cExpression\u003e \"/\" \u003cExpression\u003e\n    | \"-\" \u003cExpression\u003e\n\n\u003cConstant\u003e ::= \u003cFloat\u003e | \u003cInteger\u003e\n\n\u003cDate\u003e ::= \"'\"\u003cYear\u003e\"-\"\u003cMonth\u003e\"-\"\u003cDay\u003e\"'\"\n\n\u003cMarketName\u003e ::= \"'\"\u003cMarketName\u003e\u003cNameChar\u003e | \u003cNameChar\u003e\"'\"\n\n\u003cYear\u003e ::= \u003cDigit\u003e\u003cDigit\u003e\u003cDigit\u003e\u003cDigit\u003e\n\n\u003cMonth\u003e ::= \u003cDigit\u003e\u003cDigit\u003e\n\n\u003cDay\u003e ::= \u003cDigit\u003e\u003cDigit\u003e\n\n\u003cFloat\u003e ::= \u003cInteger\u003e\".\"\u003cInteger\u003e\n\n\u003cInteger\u003e ::= \u003cInteger\u003e\u003cDigit\u003e | \u003cDigit\u003e\n\n\u003cNameChar\u003e ::= \"A\" | \"B\" | \"C\" | \"D\" | \"E\" | \"F\" |\n               \"G\" | \"H\" | \"I\" | \"J\" | \"K\" | \"L\" |\n               \"M\" | \"N\" | \"O\" | \"P\" | \"Q\" | \"R\" |\n               \"S\" | \"T\" | \"U\" | \"V\" | \"W\" | \"X\" |\n               \"Y\" | \"Z\" | \"_\" | \u003cDigit\u003e\n\n\u003cDigit\u003e ::= \"0\" | \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"7\" | \"8\" | \"9\"\n```\n\n\n### Semantics\n\nIn the definitions below, Quant DSL expression `v` defines a\nfunction `[[v]](t)` from present time `t` to a random\nvariable in a probability space.\n\nConstant interest rate `r` is used in discounting settlements.\n\nFor market `i`, the last price `Si` and volatility `σi` are determined\nusing only market price data generated before `t0`. Brownian\nmotion `z` is used in diffusion. In the software, this `Market` semantic is\na user option, so that other market models can be used to simulate prices,\nfor example models with multiple factors, mean reversion, and jump diffusion.\n\nChoices are made using conditioning, expectation `E` is conditioned\non filtration `F` at `t`, so that choices are made only with\ninformation at the time of the choice. In practice, information is \"lost\"\nfrom included expressions by fitting the value of the expression (a\nrandom variable in a probability space) to the underlying simulated \nprices at that time using least squares. This leads to a quantitative\ndifference from maximisation (\"Max\").\n\n```\n[[Settlement(d, x)]](t) = e ** (r * (t−d)) * [[x]](t)\n```\n\n```\n[[Fixing(d, x)]](t) = [[x]](d)\n```\n\n```\n[[Market(i)]](t) = Si * e ** (σi * z(t − t0)) − 0.5 * σi ** 2 * (t − t0)\n```\n\n```\n[[Wait(d, x)]](t) = [[Settlement(d, Fixing(d, x))]](t)\n```\n\n```\n[[Choice(x, y)]](t) = max(E[[[x]](t) | F(t)], E[[[y]](t) | F(t)])\n```\n\n```\n[[Max(x, y)]](t) = max([[x]](t), [[y]](t))\n```\n\n```\n[[x + y]](t) = [[x]](t) + [[y]](t)\n```\n\n```\n[[x - y]](t) = [[x]](t) - [[y]](t)\n```\n\n```\n[[x * y]](t) = [[x]](t) * [[y]](t)\n```\n\n```\n[[x / y]](t) = [[x]](t) / [[y]](t)\n```\n\n```\n[[-x]](t) = -[[x]](t)\n```\n\nTodo: Format the above as proper math symbols.\n\nPlease note, these default semantics can be substituted\nusing the `dsl_classes` arg of `calc()` below.\n\n\n### Software\n\nThe scope of the work of a quantitative analyst involves modelling optionality,\nsimulating future prices, and evaluating the model against the simulation. In\nimplementing the syntax and semantics of Quant DSL, this software provides an\napplication object class `QuantDslApplication` which has methods that support\nthis work: `compile()`, `simulate()`, and `evaluate()`.\n\nDuring *compilation* of Quant DSL source code, the application `QuantDslApplication` constructs a\ngraph of Quant DSL expressions. The *simulation* is generated by a calibrated price process,\naccording to requirements derived from the compiled dependency graph. During *evaluation*, the nodes of\nthe dependency graph are evaluated when they are ready to be evaluated. Intermediate results\nare discarded as soon as they are no longer required, such that memory usage is mostly constant during\nevaluation. For the \"greeks\", nodes are selectively re-evaluated with perturbed values,\naccording to the periods and markets they involve, avoiding unnecessary computation.\n\nIn addition to the Quant DSL expressions above, function `def`\nstatements are supported. User defined functions can be used\nto refactor complex Quant DSL expressions, in order to model complex\noptionality concisely. The `import` statement is also supported, so Quant\nDSL function definitions and expressions can be developed and maintained as\nPython files. Since Quant DSL syntax is a strict subset of Python, the\nfull power of Python IDEs can be used to write, navigate and refactor\nQuant DSL source code.\n\n\n## Examples\n\n### calc()\n\nThe examples below use the library function `calc()` to evaluate Quant DSL source code. `calc()` uses the\nmethods of the `QuantDslApplication` mentioned above.\n\n```python\nfrom quantdsl.calculate import calc\n```\n\nWhen called, the function `calc()` returns a results object, with an attribute `fair_value` that is the\nsimulated value, under the semantics, of the given Quant DSL expression.\n\n```python\nresults = calc(source_code=\"2 + 3\")\nassert results.fair_value == 5\n```\n\nThe function `calc()` has many optional arguments that can be used to control the evaluation of an expression. \n\n**`source_code`**\n\nThe Quant DSL module that will be evaluated. It must contain one expression, and may\ncontain many function definitions. It may also contain import statements that import\nfunctions from Quant DSL modules saved as normal Python files.\n\n**`observation_date`**\n\nSets the time `t0` in the semantics from when the forward curve is evolved by the price process.\nAlso conditions the effective present time `t` of the outermost element in \nan evaluation.\n\n**`interest_rate`**\n\nThe continuously compounding short run risk free rate, used for discounting (default `0`).\n\n**`price_process`**\n\nThe name and configuration parameters for the price process that will estimate\n(by simulation) prices that could be agreed in the future.\n\n**`periodisation`**\n\nDelta granularity can be set with the `periodisation` arg of `calc()` e.g. `'daily'` or `'monthly'`.\n\nTodo: Rename the arg to `delta_granularity`.\n\n**`is_double_sided_deltas`**\n\nEvaluate with either single- or double-sided deltas (default `True`).\n\n**`path_count`**\n\nDetermines the accuracy of the simulated random variables (default `20000`).\n\n**`perturbation_factor`**\n\nUsed to calculate \"greeks\". If the `path_count` is larger, a smaller\nperturbation factor may give better result (default `0.01`).\n\n**`max_dependency_graph_size`**\n\nSets the limit on the maximum number of nodes the can be compiled from Quant DSL source with the\n`max_dependency_graph_size` arg of `calc()`.\n\n**`timeout`**\n\nYou can set a calculation to `timeout` after a given number of seconds.\n\n**`dsl_classes`**\n\nCustom DSL classes can be passed in using the `dsl_classes` argument of `calc()`.\n\n**`is_verbose`**\n\nSetting `is_verbose` will cause progress of a calculation to\nbe printed to standard output.\n\n\n### Settlement\n\nThe `Settlement` element discounts the value of the included `Expression` from its given `Date` to the effective \npresent time `t` when the element is evaluated.\n\n```\n\u003cSettlement\u003e ::= \"Settlement(\" \u003cDate\u003e \", \" \u003cExpression\u003e \")\"\n```\n\n```\n[[Settlement(d, x)]](t) = e ** (r * (t−d)) * [[x]](t)\n```\n\nFor example, with a continuously compounding `interest_rate` of `2.5` percent per year, the value `10` settled on \n`'2111-1-1'` has a present value of `82.08` on `'2011-1-1'`.\n\n```python\nresults = calc(\"Settlement('2111-1-1', 1000)\",\n    observation_date='2011-1-1',\n    interest_rate=2.5,\n)\n\nassert round(results.fair_value, 2) == 82.08\n```\n\nSimilarly, the value of `82.085` settled in `'2011-1-1'` has a present value of `1000.00` on `'2111-1-1'` \n.\n\n```python\nresults = calc(\"Settlement('2011-1-1', 82.085)\",\n    observation_date='2111-1-1',\n    interest_rate=2.5,\n)\n\nassert round(results.fair_value, 2) == 1000.00\n```\n\nDiscounting is a function of the `interest_rate` and the duration in time between the date of the `Settlement` \nelement and the effective present time `t` of its evaluation. The formula used for discounting by the `Settlement` \nelement is `e**-rt`. The `interest_rate` is the therefore the continuously compounding risk free rate (not the \nannual equivalent rate).\n\n\n### Fixing\n\nThe `Fixing` element simply conditions the effective present time of its included `Expression` with its given `Date`.\n\n```\n\u003cFixing\u003e ::= \"Fixing(\" \u003cDate\u003e \",\" \u003cExpression\u003e \")\"\n```\n\n```\n[[Fixing(d, x)]](t) = [[x]](d)\n```\n\nFor example, if a `Fixing` element includes a `Settlement` element, then the effective present time of the \nincluded `Settlement` element will be the given date of the `Fixing`.\n\nThe expression below represents the present value in `'2051-1-1'` of the value of `1000` to be settled on \n`'2111-1-1'`.\n\n```python\nresults = calc(\"Fixing('2051-1-1', Settlement('2111-1-1', 1000))\",\n    interest_rate=2.5,\n)\n\nassert round(results.fair_value, 2) == 223.13\n\n```   \n\n\n### Market\n\nThe `Market` element effectively estimates prices that could be agreed in the future.\n\n```\n\u003cMarket\u003e ::= \"Market(\" \u003cMarketName\u003e \")\"\n```\n\n```\n[[Market(i)]](t) = Si * e ** (σi * z(t − t0)) − 0.5 * σi ** 2 * (t − t0)\n```\n\nWhen a `Market` element is evaluated, it returns a random variable selected from a simulation\n of market prices.\n\nSelecting an estimated price from the simulation requires the name of the market,\na *fixing date* (when the price would be agreed), and a *delivery date* (when the goods would be delivered).\n \nThe name of the `Market` is included in the element (e.g. `'GAS'` or `'POWER'`). Both the fixing date and\nthe delivery date are determined by the effective present time when the element is evaluated.\n\n\n#### Simulation\n \nThe price simulation is generated by a price process. In this example, the library's one-factor\nmulti-market Black Scholes price process `BlackScholesPriceProcess` is used to generate correlated\ngeometric Brownian motions.\n\nThe calibration parameters required by `BlackScholesPriceProcess` are `market` (a list of market names), and \n`sigma`, (a list of annualised historical volatilities, expressed as a fraction of 1, rather than as a \npercentage).\n\n```python\nprice_process = {\n    'name': 'quantdsl.priceprocess.blackscholes.BlackScholesPriceProcess',\n    'market': ['GAS', 'POWER'],\n    'sigma': [0.02, 0.02],\n    'rho': [\n        [1.0, 0.8],\n        [0.8, 1.0]\n    ],\n    'curve': {\n        'GAS': [\n            ('2011-1-1', 10),\n            ('2111-1-1', 1000)\n        ],\n        'POWER': [\n            ('2011-1-1', 11),\n            ('2111-1-1', 1100)\n        ]\n    },\n}\n```\n\nWhen a simulation generated by this price process involves two or more markets, an additional\nparameter `rho` is  required, which represents the correlation between the markets (a symmetric\nmatrix expressed as a list of lists).\n\n\n#### Forward curve\n\nA forward `curve` is required by the price process to provide estimates of current prices for each market at the given \n`observation_date`. The prices in the forward curve are prices that can be agreed at the `observation_date` for \ndelivery at the specified dates. These prices are then diffused according to the risk-neutral dynamics of the\nprice process.\n\nRequirements for the simulation (dates and markets) are derived from the expression to be evaluated. If the \nexpression involves observing at a future date the price for particular goods to be delivered at a particular \ndate, then the simulation will provide a random variable which simulates that observation.\n\nA `Market` element evaluated at the `observation_date` will simply return the last value from the given forward\n curve for that market at the given `observation_date`.\n \n```python\nresults = calc(\"Market('GAS')\",\n    observation_date='2011-1-1',\n    price_process=price_process,\n)\n```\n\nSince the `Market` element uses random variables from the price simulation, so the results are random variables, and\n we need to take the `mean()` to obtain a scalar value.\n\n```python\nassert results.fair_value.mean() == 10\n```\n\nIf the forward curve doesn't contain a price at the required delivery date, a price at \nan earlier delivery date is used (with zero order hold).\n\n```python\nresults = calc(\"Market('GAS')\",\n    observation_date='2012-3-4',\n    price_process=price_process,\n)\n\nassert results.fair_value.mean() == 10\n```\n\nEvaluating at a later observation date will return the later value from the forward curve.\n \n```python\nresults = calc(\"Market('GAS')\",\n    observation_date='2111-1-1',\n    price_process=price_process,\n)\nassert results.fair_value.mean() == 1000\n```\n\n#### Stochastic evolution\n\nIn the examples so far, there has been no difference between the fixing date of the `Market` element and \nthe `observation_date` of the evaluation. Therefore, there is no stochastic evolution of the forward curve, and the \nstandard deviation of the result value is zero.\n\n```python\nassert results.fair_value.std() == 0.0\n```\n\nIf a `Market` element is included within a `Fixing` element, the effective present time will be different from\nthe observation date, so the fixing date used to select a price from the simulation will be different from the\nobservation date. The simulated price will be taken from the forward curve, but will also be subjected to\nstochastic evolution.\n\nWith Brownian motion provided by the `BlackScholesPriceProcess`, the random variable used to estimate a \nprice observed in the future has a statistical distribution with non-zero standard deviation.\n\n```python\nresults = calc(\"Fixing('2051-1-1', Market('GAS'))\",\n    observation_date='2011-1-1',\n    price_process=price_process,\n)\nassert results.fair_value.std() \u003e 0.0\n```\n\n#### Accuracy\n\nThe number of samples from the distribution in the simulated random variable defaults to `20000`.\n\n```python\nassert len(results.fair_value) == 20000\n```\nThe number can be adjusted by setting `path_count`. The accuracy of results \ncan be doubled by increasing the path count by a factor of four.\n\n```python\nresults = calc(\"Market('GAS')\",\n    observation_date='2011-1-1',\n    price_process=price_process,\n    path_count=80000,\n)\nassert len(results.fair_value) == 80000\n```\n\n#### Different fixing and delivery dates\n\nThe `ForwardMarket` element can be used to specify a delivery date that is different from the fixing date (when the \nprice for that delivery would be agreed). This can be used to model trading in forward markets, and also\nto express the value of an index at maturity (see below).\n\n\n### Wait\n\nThe `Wait` element combines `Settlement` and `Fixing`, so that its `Date` is used both to condition the \neffective present time of the included `Expression`, and also the value of that expression is discounted to the \neffective present time when evaluating the `Wait` element.\n\n```\n\u003cWait\u003e ::= \"Wait(\" \u003cDate\u003e \",\" \u003cExpression\u003e \")\"\n```\n\n```\n[[Wait(d, x)]](t) = [[Settlement(d, Fixing(d, x))]](t)\n```\n\nFor example, the present value at the `observation_date` of `'2011-1-1'` of one unit of `'GAS'` delivered on \n`'2111-1-1'` is approximately `82.18`. The `Wait` element sets the delivery date of the `Market` element,\n which is used to pick the value `1000` from the forward curve. The `Wait` element also sets the fixing date\n of the `Market` element, which is used to control the stochastic evolution of the forward value.  The evolved\n value is then discounted by the `Wait` element from `2111` to the observation date `2011`.\n\n\n```python\nimport scipy\n# Setting random seed makes test results repeatable.\nscipy.random.seed(1234)\n\nresults = calc(\"Wait('2111-1-1', Market('GAS'))\",\n    price_process=price_process,\n    observation_date='2011-1-1',\n    interest_rate=2.5,\n)\n\nassert round(results.fair_value.mean(), 2) == 82.18\nassert round(results.fair_value.std(), 2) == 16.46\n```\n\n### Choice\n\nThe `Choice` element uses the least-squares Monte Carlo approach (Longstaff\nSchwartz, 1998) to compare the conditional expected value of each alternative `Expression`.\n\n```\n\u003cChoice\u003e ::= \"Choice(\" \u003cExpression\u003e \",\" \u003cExpression\u003e \")\"\n```\n\n```\n[[Choice(x, y)]](t) = max(E[[[x]](t) | F(t)], E[[[y]](t) | F(t)])\n```\n\nFor example, the value of the choice at `observation_date` of `'2011-1-1'` between one unit of `'GAS'` either on \n`'2051-1-1'` or `'2111-1-1'` is `217.39`.\n\n```python\nsource_code = \"\"\"\nChoice(\n    Wait('2051-1-1', Market('GAS')),\n    Wait('2111-1-1', Market('GAS'))\n)\n\"\"\"\n\nresults = calc(source_code,\n    observation_date='2011-1-1',\n    price_process=price_process,\n    interest_rate=2.5,\n)\nassert round(results.fair_value.mean(), 2) == 82.06\n```\n\nWhen the `Choice` element is evaluated, the value of each alternative is\nregressed as a random variable by least squares with second order polynomial regression\nto the underlying simulated values at the effective present time of the choice.\nThe choice of alternative on each path is then made using the regressed value (the\n\"conditional expected value\") but the chosen value on each path is taken from the\nunregressed value of the chosen alternative for that path (the \"expected continuation\nvalue\"). The result is a new simulated value that combines the expected continuation\nvalue of the alternatives, according to information in the simulation at the time of\nthe choice. This conditioning gives a quantitatively different result from simple\nmaximisation of the alternative expected continuation values (`Max`).\n\n\n### Function definitions\n\nQuant DSL source code can include function definitions. Expressions can involve calls to functions.\n\nWhen evaluating an expression that involves a call to a function definition, the call to the \nfunction definition is effectively replaced with the expression returned by the function definition,\nso that a larger expression is formed. Hence, the body of a function can have only one statement.\n\nThe call args of the function definition can be used as names in the function definition's\nexpressions. The call arg values will be substituted for those names in the expression when\nthe expression is returned by the function.\n\n```python\nresults = calc(\"\"\"\ndef Function(a):\n    2 * a\n\nFunction(10)\n\"\"\")\n\nassert results.fair_value == 20\n```   \n\nAlthough the function body can have only one statement, that statement can be an if-else block.\nThe call args of the function definition can be used in an if-else block, to select different\nexpressions according to the value of the function call arguments, which effectively implements\na \"case branch\".\n\nEach function call becomes a node on a dependency graph. For efficiency, each call is cached, so if a \nfunction is called many times with the same argument values (and at the same effective present time),\nthe function is only evaluated once, and the result reused. This allows branched\ncalculations to recombine efficiently. For example, the following Fibonacci function definition will\nevaluate in linear time (proportional to `n`).\n\n```python\nresults = calc(\"\"\"\ndef Fib(n):\n    if n \u003e 1:\n        Fib(n-1) + Fib(n-2)\n    else:\n        n\n\nFib(60)\n\"\"\")\n\nassert results.fair_value == 1548008755920\n```   \n\nFunction definitions can be used to refactor complex expressions. For example, if the expression is the sum of a \nseries of settlements on different dates, the expression without a function definition might be:\n\n```python\nsource_code = \"\"\"\nSettlement(Date('2011-1-1'), 10) + Settlement(Date('2011-2-1'), 10) + Settlement(Date('2011-3-1'), 10) + \\\nSettlement(Date('2011-4-1'), 10) + Settlement(Date('2011-5-1'), 10) + Settlement(Date('2011-6-1'), 10) + \\\nSettlement(Date('2011-8-1'), 10) + Settlement(Date('2011-8-1'), 10) + Settlement(Date('2011-9-1'), 10) + \\\nSettlement(Date('2011-10-1'), 10) + Settlement(Date('2011-11-1'), 10) + Settlement(Date('2011-12-1'), 10)\n\"\"\"\nresults = calc(source_code,\n    observation_date='2011-1-1',\n    interest_rate=10,\n)\nassert round(results.fair_value, 2) == 114.59\n```\n \nInstead the expression could be refactored with a function definition.\n \n```python\nsource_code = \"\"\"\ndef Settlements(start, end, installment):\n    if start \u003c= end:\n        Settlement(start, installment) + Settlements(start + TimeDelta('1m'), end, installment)\n    else:\n        0\n        \nSettlements(Date('2011-1-1'), Date('2011-12-1'), 10)\n\"\"\"\nresults = calc(source_code,\n    observation_date='2011-1-1',\n    interest_rate=10,\n)\nassert round(results.fair_value, 2) == 114.67\n```\n\n\n### European and American options\n\nIn general, an option can be expressed as waiting until an `expiry` date to choose between, on one hand, the \ndifference between the value of an `underlying` expression and a `strike` expression,\nand, on the other hand, an `alternative` expression.\n\n```python\ndef Option(expiry, strike, underlying, alternative):\n    Wait(expiry, Choice(underlying - strike, alternative))\n```\n\nA European option can then be expressed simply as an `Option` with zero alternative.\n\n```python\ndef EuropeanOption(expiry, strike, underlying):\n    Option(expiry, strike, underlying, 0)\n```\nAn American option can be expressed as an `Option` to exercise at a given `strike` price on \nthe `start` date, with the alternative being another `AmericanOption` starting on the next date - and so on until the \n`expiry` date, when the `alternative` becomes zero.\n\n```python\ndef AmericanOption(start, expiry, strike, underlying, step):\n    if start \u003c= expiry:\n        Option(\n            start, strike, underlying, AmericanOption(\n                start + step, expiry, strike, underlying, step\n            )\n        )\n    else:\n        0\n```\n\nA European put option can be expressed as a `EuropeanOption`, with negated underlying and strike expressions.\n\n```python\ndef EuropeanPut(expiry, strike, underlying):\n    EuropeanOption(expiry, -strike, -underlying)\n```\n\nA European stock option can be expressed as a `EuropeanOption`, with the `underlying` being the index at\nmaturity. The `IndexAtMaturity` has the spot price at the observation date\n(using `ForwardMarket`) observed at a time in the future, discounted forward from the observation date (due\nto the `Settlement`) to a time in the future. This \"time in the future\" is the effective\npresent time set by the `Wait` element of the `Option` definition above.\n\n```python\ndef EuropeanStockOption(expiry, strike, stock):\n    EuropeanOption(expiry, strike, IndexAtMaturity(stock))\n```\n\nThe expression `IndexAtMaturity` is passed into the `Option` definition and, due to the `Wait` \nelement in that definition, is evaluated with `expiry` as the present time. Therefore the spot \nprice is discounted forward to the `expiry`, and the fixing date of the `ForwardMarket` is `expiry`, so that the\nspot price is subjected to stochastic evolution as well as interest rates.\n\n```python\n\ndef IndexAtMaturity(stock):\n    Settlement(ObservationDate(), ForwardMarket(ObservationDate(), stock))\n```\n\nThe built-in `ObservationDate` element evaluates to the `observation_date` passed to the the `calc()` function.\n\nLet's evaluate a European stock option at different strike prices, volatilities, and interest rates.\n\nThe following function `calc_european` will make it easier to evaluate the option several times.\n\n```python\ndef calc_european(spot, strike, sigma, rate):\n    source_code = \"\"\"\ndef Option(expiry, strike, underlying, alternative):\n    Wait(expiry, Choice(underlying - strike, alternative))\n\ndef EuropeanOption(expiry, strike, underlying):\n    Option(expiry, strike, underlying, 0)\n   \ndef EuropeanStockOption(expiry, strike, stock):\n    EuropeanOption(expiry, strike, IndexAtMaturity(stock))\n\ndef IndexAtMaturity(stock):\n    Settlement(ObservationDate(), ForwardMarket(ObservationDate(), stock))\n    \nEuropeanStockOption(Date('2012-1-1'), {strike}, 'ACME')\n    \"\"\".format(strike=strike)\n    \n    results = calc(\n        source_code=source_code,\n        observation_date='2011-1-1',\n        price_process={\n            'name': 'quantdsl.priceprocess.blackscholes.BlackScholesPriceProcess',\n            'market': ['ACME'],\n            'sigma': [sigma],\n            'curve': {\n                'ACME': [\n                    ('2011-1-1', spot),\n                ]\n            },\n        },\n        interest_rate=rate,\n    )\n    return round(results.fair_value.mean(), 2)\n```\n\nIf the strike price of a European option is the same as the price of the underlying, without any volatility (`sigma` \nis `0`) the value is zero.\n\n```python\nassert calc_european(spot=10, strike=10, sigma=0, rate=0) == 0.0\n```\n\nIf the strike price is less than the underlying, without any volatility, the value is the difference between the \nstrike and the underlying.\n\n```python\nassert calc_european(spot=10, strike=8, sigma=0, rate=0) == 2.0\n```\n\nIf the strike price is greater than the underlying, without any volatility, the value is zero.\n\n```python\nassert calc_european(spot=10, strike=12, sigma=0, rate=0) == 0.0\n```\n\nIf the strike price is the same as the underlying, with some volatility in the price of the underlying, there\n is some value in the option.\n\n```python\nassert calc_european(spot=10, strike=10, sigma=0.9, rate=0) == 3.42\n```\n\nIf the strike price is less than the underlying, with some volatility in the price of the underlying (`sigma`) there\n is more value in the option than without volatility.\n\n```python\nassert calc_european(spot=10, strike=8, sigma=0.9, rate=0) == 4.23\n```\n\nIf the strike price is greater than the underlying, with some volatility in the price of the underlying (`sigma`) there\n is still a little bit of value in the option.\n\n```python\nassert calc_european(spot=10, strike=12, sigma=0.9, rate=0) == 2.90\n```\n\nThese results compare well with results from the Black-Scholes analytic formula for European stock options.\n\n\n### Gas storage\n\nAn evaluation of a gas storage facility. The value of the\ngas storage facility follows from the difference between the price\nwhen gas is injected and the price when gas is withdrawn.\n\nThe Quant DSL source code below models a gas storage facility as\na lattice of choices to inject or withdraw a quantity of gas.\nIf the facility is full, injecting gas is not an option. Similarly,\nif the facility is empty, withdrawing gas is not an option.\n\n```python\ngas_storage = \"\"\"\ndef GasStorage(start, end, market, quantity, target, limit, step):\n    if ((start \u003c end) and (limit \u003e 0)):\n        if quantity \u003c= 0:\n            Wait(start, Choice(\n                Continue(start, end, market, quantity, target, limit, step),\n                Inject(start, end, market, quantity, target, limit, step, 1),\n            ))\n        elif quantity \u003e= limit:\n            Wait(start, Choice(\n                Continue(start, end, market, quantity, target, limit, step),\n                Inject(start, end, market, quantity, target, limit, step, -1),\n            ))\n        else:\n            Wait(start, Choice(\n                Continue(start, end, market, quantity, target, limit, step),\n                Inject(start, end, market, quantity, target, limit, step, 1),\n                Inject(start, end, market, quantity, target, limit, step, -1),\n            ))\n    else:\n        if target \u003c 0 or target == quantity:\n            0\n        else:\n            BreachOfContract()\n\n\n@inline\ndef Continue(start, end, market, quantity, target, limit, step):\n    GasStorage(start + step, end, market, quantity, target, limit, step)\n\n\n@inline\ndef Inject(start, end, market, quantity, target, limit, step, vol):\n    Continue(start, end, market, quantity + vol, target, limit, step) - \\\n    vol * market\n\n\n@inline\ndef BreachOfContract():\n    -10000000000000000\n\n@inline\ndef Empty():\n    0\n\n@inline\ndef Full():\n    50000\n\n\nGasStorage(Date('2011-4-1'), Date('2012-4-1'), Market('GAS'), Empty(), Empty(), Full(), TimeDelta('1m'))\n\"\"\"\n```\n\nThis example uses a forward curve that has seasonal variation (prices are high in winter and low in \nsummer).\n\n```python\ngas = {\n    'name': 'quantdsl.priceprocess.blackscholes.BlackScholesPriceProcess',\n    'market': ['GAS'],\n    'sigma': [0.3],\n    'curve': {\n        'GAS': (\n            ('2011-1-1', 13.5),\n            ('2011-2-1', 11.0),\n            ('2011-3-1', 10.0),\n            ('2011-4-1', 9.0),\n            ('2011-5-1', 7.5),\n            ('2011-6-1', 7.0),\n            ('2011-7-1', 6.5),\n            ('2011-8-1', 7.5),\n            ('2011-9-1', 8.5),\n            ('2011-10-1', 10.0),\n            ('2011-11-1', 11.5),\n            ('2011-12-1', 12.0),\n            ('2012-1-1', 13.5),\n            ('2012-2-1', 11.0),\n            ('2012-3-1', 10.0),\n            ('2012-4-1', 9.0),\n            ('2012-5-1', 7.5),\n            ('2012-6-1', 7.0),\n            ('2012-7-1', 6.5),\n            ('2012-8-1', 7.5),\n            ('2012-9-1', 8.5),\n            ('2012-10-1', 10.0),\n            ('2012-11-1', 11.5),\n            ('2012-12-1', 12.0)\n        )\n    }\n}\n```\n\nBecause the `periodisation` argument is set to `'monthly'`, the deltas for each market in each month will be \ncalculated, and estimated risk neutral hedge positions will be printed for each market in each period, along\nwith the overall fair value.\n\n```python\nresults = calc(\n    source_code=gas_storage,\n    observation_date='2011-1-1',\n    interest_rate=2.5,\n    periodisation='monthly',\n    price_process=gas,\n    verbose=True,\n)\n\nassert round(results.fair_value.mean(), 2) == 20.78\n\nprint(results)\n```\n\nThe results, showing deltas for each month for each market, and the fair value.\n\n```\nCompiled 92 nodes \nCompilation in 0.463s\nSimulation in 0.061s\nStarting 844 node evaluations, please wait...\n844/844 100.00% complete 99.68 eval/s running 9s eta 0s\nEvaluation in 8.468s\n\n\n2011-04-01 GAS\nPrice:     9.00\nDelta:    -0.99\nHedge:     1.00 ± 0.00\nCash:     -8.94 ± 0.03\n\n2011-05-01 GAS\nPrice:     7.50\nDelta:    -0.99\nHedge:     1.00 ± 0.00\nCash:     -7.44 ± 0.03\n\n2011-06-01 GAS\nPrice:     7.00\nDelta:    -0.99\nHedge:     1.00 ± 0.00\nCash:     -6.93 ± 0.03\n\n2011-07-01 GAS\nPrice:     6.49\nDelta:    -0.99\nHedge:     1.00 ± 0.00\nCash:     -6.41 ± 0.03\n\n2011-08-01 GAS\nPrice:     7.49\nDelta:    -0.99\nHedge:     1.00 ± 0.00\nCash:     -7.38 ± 0.04\n\n2011-09-01 GAS\nPrice:     8.49\nDelta:    -0.98\nHedge:     1.00 ± 0.00\nCash:     -8.35 ± 0.04\n\n2011-10-01 GAS\nPrice:     9.97\nDelta:     0.98\nHedge:    -1.00 ± 0.00\nCash:      9.79 ± 0.05\n\n2011-11-01 GAS\nPrice:    11.47\nDelta:     0.98\nHedge:    -1.00 ± 0.00\nCash:     11.23 ± 0.07\n\n2011-12-01 GAS\nPrice:    11.98\nDelta:     0.98\nHedge:    -1.00 ± 0.00\nCash:     11.70 ± 0.07\n\n2012-01-01 GAS\nPrice:    13.46\nDelta:     0.98\nHedge:    -1.00 ± 0.00\nCash:     13.13 ± 0.09\n\n2012-02-01 GAS\nPrice:    10.98\nDelta:     0.97\nHedge:    -1.00 ± 0.00\nCash:     10.68 ± 0.07\n\n2012-03-01 GAS\nPrice:     9.99\nDelta:     0.97\nHedge:    -1.00 ± 0.00\nCash:      9.70 ± 0.07\n\nNet hedge GAS:       0.00 ± 0.00\nNet hedge cash:     20.78 ± 0.28\n\nFair value: 20.78 ± 0.28\n```\n\nThe value obtained is the extrinsic value. The intrinsic value can be \nobtained by setting the volatility `sigma` to `0`, and can be evaluated\nwith `path_count` of `1`.\n\nThe recommended hedge positions suggest injecting gas when\nthe price is low, and withdrawing when the price is high.\n\n\n### Gas fired power station\n\nAn evaluation of a gas fired power station. The value of a gas fired power\nstation follows from selling generated power whilst paying for gas. \n\nThe Quant DSL source code below models a gas fired power station as\na lattice of choices whether or not to run the power station. The efficiency\nof generation is modelled to be lower if the power station has been stopped.\n\nDispatch decisions are made daily, with gas and power traded one day ahead.\n\n\n```python\npower_plant = \"\"\"\nfrom quantdsl.semantics import Choice, Market, TimeDelta, Wait, inline, Min\n\n\ndef PowerPlant(start, end, temp):\n    if (start \u003c end):\n        Wait(start, Choice(\n            PowerPlant(Tomorrow(start), end, Hot()) + ProfitFromRunning(start, temp),\n            PowerPlant(Tomorrow(start), end, Stopped(temp))\n        ))\n    else:\n        return 0\n\n@inline\ndef Power(start):\n    DayAhead(start, 'POWER')\n\n@inline\ndef Gas(start):\n    DayAhead(start, 'GAS')\n\n@inline\ndef DayAhead(start, name):\n    ForwardMarket(Tomorrow(start), name)\n    \n@inline\ndef Tomorrow(start):\n    start + TimeDelta('1d')\n\n@inline\ndef ProfitFromRunning(start, temp):\n    if temp == Cold():\n        return 0.3 * Power(start) - Gas(start)\n    elif temp == Warm():\n        return 0.6 * Power(start) - Gas(start)\n    else:\n        return Power(start) - Gas(start)\n\n@inline\ndef Stopped(temp):\n    if temp == Hot():\n        Warm()\n    else:\n        Cold()\n\n@inline\ndef Hot():\n    2\n\n@inline\ndef Warm():\n    1\n\n@inline\ndef Cold():\n    0\n    \n\nPowerPlant(Date('2012-1-1'), Date('2012-1-5'), Cold())\n\"\"\"\n```\n\nThe prices process is calibrated with two correlated markets.\n\n```python\ngas_and_power = {\n    'name': 'quantdsl.priceprocess.blackscholes.BlackScholesPriceProcess',\n    'market': ['GAS', 'POWER'],\n    'sigma': [0.3, 0.3],\n    'rho': [[1.0, 0.8], [0.8, 1.0]],\n    'curve': {\n            'GAS': [\n                ('2012-1-1', 11.0),\n                ('2012-1-2', 11.0),\n                ('2012-1-3', 1.0),\n                ('2012-1-4', 1.0),\n                ('2012-1-5', 11.0),\n            ],\n            'POWER': [\n                ('2012-1-1', 1.0),\n                ('2012-1-2', 1.0),\n                ('2012-1-3', 11.0),\n                ('2012-1-4', 11.0),\n                ('2012-1-5', 11.0),\n            ]\n        }\n}\n```\n\nBecause the `periodisation` is set to `'daily'`, the deltas for each market in each day will be \ncalculated, and estimated risk neutral hedge positions will be printed for each market in each period, along\nwith the overall fair value.\n\n```python\nresults = calc(\n    source_code=power_plant,\n    observation_date='2011-1-1',\n    interest_rate=2.5,\n    periodisation='daily',\n    price_process=gas_and_power,\n    verbose=True\n)\n\nassert round(results.fair_value.mean(), 2) == 12.82\n\nprint(results)\n```\n\nThese are the results, showing monthly deltas for each of the two markets.\nThe recommended hedge positions suggest running the plant when\nthe price of power is high and the price of gas is low. The relative\ninefficiency of running the plant from `Cold()` is reflected in the delta\nfor `POWER` in `2012-01-03`.\n\n\n```\nCompiled 16 nodes \nCompilation in 0.038s\nSimulation in 0.043s\nStarting 112 node evaluations, please wait...\n112/112 100.00% complete 115.57 eval/s running 1s eta 0s\nEvaluation in 0.970s\n\n\n2012-01-02 GAS\nPrice:    10.98\nDelta:    -0.05\nHedge:     0.05 ± 0.00\nCash:     -0.41 ± 0.04\n\n2012-01-02 POWER\nPrice:     1.00\nDelta:     0.02\nHedge:    -0.02 ± 0.00\nCash:      0.02 ± 0.00\n\n2012-01-03 GAS\nPrice:     1.00\nDelta:    -0.98\nHedge:     1.00 ± 0.00\nCash:     -0.97 ± 0.01\n\n2012-01-03 POWER\nPrice:    10.98\nDelta:     0.32\nHedge:    -0.33 ± 0.00\nCash:      3.64 ± 0.05\n\n2012-01-04 GAS\nPrice:     1.00\nDelta:    -0.98\nHedge:     1.00 ± 0.00\nCash:     -0.97 ± 0.01\n\n2012-01-04 POWER\nPrice:    10.98\nDelta:     0.98\nHedge:    -1.00 ± 0.00\nCash:     10.71 ± 0.07\n\n2012-01-05 GAS\nPrice:    10.98\nDelta:    -0.49\nHedge:     0.50 ± 0.00\nCash:     -4.95 ± 0.11\n\n2012-01-05 POWER\nPrice:    10.98\nDelta:     0.49\nHedge:    -0.50 ± 0.00\nCash:      5.76 ± 0.13\n\nNet hedge GAS:       2.55 ± 0.01\nNet hedge POWER:    -1.85 ± 0.01\nNet hedge cash:     12.82 ± 0.10\n\nFair value: 12.82 ± 0.10\n```\n\n## Jupyter notebooks\n\nIt's easy to use Quant DSL in a Jupyter notebook. See [example_notebook.ipynb](./example_notebook.ipynb).\n\nJupyter notebooks can be executed on a Jupyter hub.\n\n\n## Library\n\nThere is a small collection of Quant DSL modules in a library under `quantdsl.lib`.\nPutting Quant DSL source code in dedicated Python files makes it much easier to use\na Python IDE to develop and maintain Quant DSL function definitions.\n\n\n## Acknowledgments\n\nThe Quant DSL language was partly inspired by the paper\n*[Composing contracts: an adventure in financial engineering (functional pearl)](\nhttp://research.microsoft.com/en-us/um/people/simonpj/Papers/financial-contracts/contracts-icfp.htm\n)* by Simon Peyton Jones and others. The idea of orchestrating evaluations with a dependency graph,\nto help with parallel and distributed execution, was inspired by a [talk about dependency graphs by\nKirat Singh](https://www.youtube.com/watch?v=lTOP_shhVBQ). This implementation uses NumPy and\nSciPy packages, and the Python ast (\"Absract Syntax Trees\") module. We have also been encourged by members of the \n[London Financial Python User Group](https://www.google.co.uk/search?q=London+Financial+Python+User+Group),\nwhere Quant DSL expression syntax and semantics were first presented.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnbywater%2Fquantdsl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnbywater%2Fquantdsl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnbywater%2Fquantdsl/lists"}