{"id":51116335,"url":"https://github.com/arpjw/skew","last_synced_at":"2026-06-24T22:30:25.624Z","repository":{"id":365421248,"uuid":"1271982795","full_name":"arpjw/skew","owner":"arpjw","description":"Typed DSL for compositional financial contracts in OCaml","archived":false,"fork":false,"pushed_at":"2026-06-17T08:06:17.000Z","size":3712,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T10:08:10.434Z","etag":null,"topics":["automatic-differentiation","binomial-tree","derivatives","dsl","financial-engineering","monte-carlo","ocaml","options-pricing","quantitative-finance","type-theory"],"latest_commit_sha":null,"homepage":"https://skew.aryasomu.com","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arpjw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-17T07:20:56.000Z","updated_at":"2026-06-17T08:07:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/arpjw/skew","commit_stats":null,"previous_names":["arpjw/skew"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/arpjw/skew","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arpjw%2Fskew","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arpjw%2Fskew/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arpjw%2Fskew/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arpjw%2Fskew/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arpjw","download_url":"https://codeload.github.com/arpjw/skew/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arpjw%2Fskew/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34752465,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["automatic-differentiation","binomial-tree","derivatives","dsl","financial-engineering","monte-carlo","ocaml","options-pricing","quantitative-finance","type-theory"],"created_at":"2026-06-24T22:30:19.390Z","updated_at":"2026-06-24T22:30:25.613Z","avatar_url":"https://github.com/arpjw.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Skew\n\nA typed DSL for compositional financial contracts in OCaml. Contracts are expressed as\nalgebraic combinations of primitives — `Zero`, `One`, `Give`, `And`, `Or`, `Scale`,\n`Get`, `Truncate`, `Anytime` — and priced by two independent backends: a Monte Carlo\nsimulator and a Cox-Ross-Rubinstein binomial lattice. Greeks are computed via forward-mode\nautomatic differentiation implemented from scratch using dual numbers, without finite\ndifferencing.\n\nInspired by Peyton Jones \u0026 Eber (2000),\n[Composing Contracts: An Adventure in Financial Engineering](https://www.microsoft.com/en-us/research/publication/composing-contracts-an-adventure-in-financial-engineering/).\nThis is not a port — it is a from-scratch OCaml implementation that extends the original\ncombinators with a GADT observable language, a currency type system, forward-mode AD for\nGreeks, and a normalization rewrite system.\n\n[Playground →](https://skew.aryasomu.com) · [Source](https://github.com/arpjw/skew)\n\n---\n\n## Contract Language\n\nThe pricing engine knows nothing about \"call options.\" It only knows how to evaluate nine\nprimitive combinators. `european_call` is not a built-in — it is a definition:\n\n```ocaml\nlet european_call underlying ccy strike expiry =\n  Truncate (expiry,\n    Get (\n      Or (\n        Scale (Observable.(spot underlying -. konst strike), One ccy),\n        Zero)))\n```\n\nA call is: at most until `expiry` (`Truncate`), acquire (`Get`) the better of\n(`Or`) receiving `S − K` units of currency or nothing (`Zero`). The pricer never sees\n\"call\" — it sees `Or`, `Scale`, `Get`. This compositionality is the point: spreads,\ncollars, barriers, swaps, and structured products all reduce to the same nine forms.\n\n### Combinator Reference\n\n| Combinator | Meaning |\n|---|---|\n| `Zero` | No cashflows. Additive identity. |\n| `One ccy` | Receive 1 unit of `ccy` now. |\n| `Give c` | Flip all cashflows of `c`. |\n| `And (c1, c2)` | Hold both contracts. Cashflows are additive. |\n| `Or (c1, c2)` | Holder chooses the better contract at acquisition. |\n| `Scale (obs, c)` | Multiply all cashflows of `c` by the observable `obs`. |\n| `Truncate (t, c)` | `c` expires worthless after date `t`. |\n| `Then (c1, c2)` | Activate `c2` when `c1` expires. |\n| `Get c` | Acquire `c` at the first date it has non-negative value. |\n| `Anytime c` | Like `Get` but holder chooses when. American semantics. |\n\n### Derived Contracts\n\n| Contract | Definition | REPL syntax |\n|---|---|---|\n| European call | `Truncate(T, Get(Or(Scale(S−K, One ccy), Zero)))` | `call AAPL 150.0 2026-12-19` |\n| European put | `Truncate(T, Get(Or(Scale(K−S, One ccy), Zero)))` | `put AAPL 150.0 2026-12-19` |\n| American call | `Truncate(T, Anytime(Or(Scale(S−K, One ccy), Zero)))` | `acall AAPL 150.0 2026-12-19` |\n| American put | `Truncate(T, Anytime(Or(Scale(K−S, One ccy), Zero)))` | `aput AAPL 150.0 2026-12-19` |\n| Forward | `Truncate(T, Get(And(Scale(S, One ccy), Give(Scale(K, One ccy)))))` | `forward AAPL 150.0 2026-12-19` |\n| Zero-coupon bond | `Scale(N, Truncate(T, Get(One ccy)))` | `zcb USD 2027-01-01 100.0` |\n\n---\n\n## Design\n\n### Typed Observables via GADTs\n\nThe observable language is parameterized by its value type:\n\n```ocaml\ntype _ t =\n  | Const    : 'a -\u003e 'a t\n  | Lift1    : ('a -\u003e 'b) * 'a t -\u003e 'b t\n  | Lift2    : ('a -\u003e 'b -\u003e 'c) * 'a t * 'b t -\u003e 'c t\n  | Spot     : string -\u003e float t\n  | Rate     : Currency.t -\u003e float t\n  | Greater  : float t * float t -\u003e bool t\n  | Equal    : float t * float t -\u003e bool t\n  | If       : bool t * 'a t * 'a t -\u003e 'a t\n  | Date     : Date.t t\n  | Horizon  : Date.t -\u003e float t\n```\n\n`Scale` accepts `float Observable.t` — the type parameter enforces this at compile time.\nPassing a `bool` observable to `Scale` is a type error the compiler catches before the\ncode runs. This is the same technique used in Jane Street's `Incremental` and\n`typed_fields`.\n\nEvaluation is a single structural recursion over the GADT. No runtime type checks. No\n`Obj.magic`. The type system does the work.\n\n### Contract Simplification\n\nA rewrite system normalizes contracts before pricing. Rules are applied bottom-up in a\nsingle post-order pass, repeated to fixed point:\n\n| Rule | Reduction |\n|---|---|\n| `Give (Give c)` | `c` |\n| `And (Zero, c)` | `c` |\n| `And (c, Zero)` | `c` |\n| `Scale (0, _)` | `Zero` |\n| `Scale (1, c)` | `c` |\n| `Scale (_, Zero)` | `Zero` |\n| `Give Zero` | `Zero` |\n| `Truncate (_, Zero)` | `Zero` |\n| `Then (Zero, c)` | `c` |\n| `Get Zero` | `Zero` |\n| `Anytime Zero` | `Zero` |\n| `Scale (a, Scale (b, c))` | `Scale (a*b, c)` — constant folding |\n| `And (And (a, b), c)` | `And (a, And (b, c))` — right-associate |\n\nTermination is guaranteed because every rule either reduces AST size or holds it\nconstant. The fixed-point loop runs until a full pass produces no rewrites.\n\n```\nskew\u003e :simplify scale 1.0 (give (give (and zero (one USD))))\nBefore : Scale(1.0, Give(Give(And(Zero, One(USD)))))\nAfter  : One(USD)\n```\n\n### Currency Type Checker\n\nBefore any contract reaches the pricer, a type checker infers and unifies currency types\nacross the AST. The rules mirror Hindley-Milner: propagate through wrappers (`Give`,\n`Scale`, `Get`, `Anytime`, `Truncate`), unify at combination points (`And`, `Or`,\n`Then`). `Void` (from `Zero`) is compatible with any currency; two distinct non-void\ncurrencies in the same `And` or `Or` is an error.\n\n```\nskew\u003e :check and (one USD) (one EUR)\nError: CurrencyMismatch in And:\n  Left  : Single(USD)\n  Right : Single(EUR)\n  Contracts with different currencies cannot be combined without an FX leg.\n```\n\nThe pricer refuses to run on contracts that fail the check.\n\n### Dual-Number Forward-Mode AD\n\nGreeks are computed by forward-mode automatic differentiation, not finite differencing.\nA dual number carries a primal and a directional derivative:\n\n```ocaml\ntype t = { v: float; d: float }\n\nlet ( *. ) a b = { v = a.v *. b.v; d = a.d *. b.v +. a.v *. b.d }\nlet exp a = let e = Float.exp a.v in { v = e; d = a.d *. e }\nlet sqrt a = let s = Float.sqrt a.v in { v = s; d = a.d /. (2.0 *. s) }\n```\n\nTo compute delta: set `d = 1.0` on the spot observable and evaluate the pricing function\nonce. The `d` component of the output is the exact pathwise derivative — not an\napproximation. The dual arithmetic automatically propagates `∂/∂S` through every\narithmetic operation in the payoff.\n\nVega, theta, and rho use central finite difference (bump-and-reval) because those\nparameters enter through the path simulation, not through observable expressions, and\ncannot be cleanly lifted to dual numbers.\n\nThe dual library is implemented from scratch: addition, subtraction, multiplication,\ndivision, `exp`, `log`, `sqrt`, `pow`, `max`, `min`, and `norm_cdf` (for\nBlack-Scholes closed-form verification), all overloaded to propagate derivatives.\n\n### Two Pricing Backends behind One Interface\n\nBoth backends satisfy the same module type:\n\n```ocaml\nmodule type PRICER = sig\n  type config\n  val default_config : config\n  val price           : config:config -\u003e market:Market.t -\u003e contract:Contract.t -\u003e float\n  val price_with_stderr : config:config -\u003e market:Market.t -\u003e contract:Contract.t -\u003e float * float\nend\n```\n\n**Monte Carlo** (`MonteCarlo : PRICER`): simulates GBM paths using the Box-Muller\ntransform for standard normals and exact log-normal steps between observation dates:\n\n```\nS(t+dt) = S(t) · exp((r − ½σ²)dt + σ√dt · Z),  Z ~ N(0,1)\n```\n\nDefault: 10,000 paths, 252 steps. Returns mean and standard error. Handles arbitrary\ncontract trees by structural recursion.\n\n**Lattice** (`LatticePricer : PRICER`): Cox-Ross-Rubinstein binomial tree with:\n\n```\nu = exp(σ√dt),   d = 1/u,   p_u = (exp(r·dt) − d) / (u − d)\n```\n\nForward pass fills the price tree. Backward induction discounts expected values,\nwith an early-exercise check at each node for American options (`Anytime`):\n\n```\nV(i,j) = max(payoff(S(i,j)), e^{−r·dt} · (p_u · V(i+1,j+1) + p_d · V(i+1,j)))\n```\n\nDefault: 500 steps. Recognizes standard contract forms (European/American call/put,\n`Give`, `Scale`) and falls back to Monte Carlo for unsupported trees with a warning.\n\n### Market Data\n\n`Market.t` holds spot prices, flat or surface-interpolated vols, and risk-free rates per\ncurrency. Vol surface interpolation is bilinear: find the bracketing strike and expiry\nindices, interpolate in the strike direction at each expiry, then interpolate the results\nin the expiry direction. Values outside the grid clamp to the boundary.\n\n---\n\n## REPL\n\n```\nskew\u003e :set spot AAPL 155.0\nMarket: AAPL spot = 155.0000\n\nskew\u003e :set vol AAPL 0.25\nMarket: AAPL vol = 0.2500\n\nskew\u003e :set rate USD 0.05\nMarket: USD rate = 5.00%\n\nskew\u003e :price call AAPL 150.0 2026-12-19\nPrice    :  14.2300  (±0.0900, N=10000)\n\nskew\u003e :lattice call AAPL 150.0 2026-12-19\nPrice    :  14.3100  (N=500 steps)\n\nskew\u003e :both call AAPL 150.0 2026-12-19\nMC     :  14.2300  (±0.0900)\nLattice:  14.3100\nDiff   :   0.0800  (0.56%)\n\nskew\u003e :greeks call AAPL 150.0 2026-12-19 AAPL\nContract : EuropeanCall(AAPL, USD, 150.00, 2026-12-19)\n──────────────────────────────────────────────────────\nPrice    :  14.2300  (±0.0900)\nDelta    :   0.6410\nVega     :   0.4120  (per 1 vol pt)\nTheta    :  -0.0210  (per day)\nRho      :   0.0060  (per 1bp)\n\nskew\u003e :check and (one USD) (one EUR)\nError: CurrencyMismatch in And:\n  Left  : Single(USD)\n  Right : Single(EUR)\n\nskew\u003e :simplify give (give (scale 1.0 (and zero (one USD))))\nBefore : Give(Give(Scale(1.0, And(Zero, One(USD)))))\nAfter  : One(USD)\n```\n\nCommands: `:price`, `:lattice`, `:both`, `:greeks`, `:check`, `:simplify`,\n`:set spot/vol/rate`, `:show market`, `:show contract`, `:help`, `:quit`.\n\n---\n\n## Browser Playground\n\nThe same OCaml library compiles to JavaScript via\n[js_of_ocaml](https://ocsigen.org/js_of_ocaml/). The entire pricing engine — GBM\nsimulation, lattice, dual-number Greeks — runs in the browser with no server.\n\nTo keep the UI responsive, all computation runs in a\n[Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). The\nmain thread dispatches a request and updates the DOM when results arrive; it is never\nblocked. A request queue ensures rapid typing replaces stale pending work rather than\nstacking it.\n\nThe web build uses 1,000 paths / 52 steps (vs 10,000 / 252 for the native REPL) for\ninteractive latency. The native binary and the browser bundle share the same `skew_lib`\n— the only platform-specific code is in `bin/main.ml` and `web/main_js.ml`.\n\n---\n\n## Test Suite\n\n68 tests across 11 modules, run with Alcotest. Selected numerical benchmarks:\n\n| Test | What it checks |\n|---|---|\n| MC European call | Within $0.50 of Black-Scholes at N=50,000 |\n| Lattice European call | Within $0.05 of BS at N=500 steps |\n| Lattice European put | Within $0.05 of BS at N=500 steps |\n| Put-call parity (MC) | `C − P = S − K·e^{−rT}` within $0.20 |\n| Put-call parity (lattice) | Same identity within $0.01 |\n| MC vs lattice | Agree within $0.30 on same contract |\n| American put ≥ European put | Early exercise premium is non-negative |\n| Delta (MC vs BS) | Within 0.02 of analytical delta |\n| Zero-coupon bond | Within $0.01 of `N·e^{−rT}` |\n| Simplification fixed-point | All rewrite rules verified individually |\n| Currency checker | Mismatch detection for all combinator forms |\n\n---\n\n## Architecture\n\n```\ncurrency ──► date ──► observable ──► contract ──► checker\n                                              └──► simplify\nmarket ──► dual ──► path ──► pricer (MonteCarlo)  ──► greeks\n                └──► lattice ──► pricer (Lattice)  └──► print ──► repl\n```\n\n---\n\n## Known Limitations\n\n1. **Correlation**: Multi-asset simulation uses independent GBM paths (ρ = 0). Correlated\n   simulation via Cholesky decomposition is not implemented.\n\n2. **Longstaff-Schwartz**: American options on Monte Carlo paths use simplified exercise.\n   Use the lattice backend for American options.\n\n3. **Smile**: The vol surface supports bilinear interpolation but not stochastic vol\n   (Heston, SABR). Vols are flat unless the surface is explicitly populated.\n\n4. **Dividends**: Discrete and continuous dividends are not modeled.\n\n5. **Pathwise delta on discontinuous payoffs**: Binary options, barriers, and contracts\n   with kinks produce biased pathwise deltas. Use bump-and-reval for those.\n\n---\n\n## Build\n\n```bash\nopam install . --deps-only\ndune build\ndune exec test/test_suite.exe   # 68 tests\ndune exec bin/main.exe          # REPL\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farpjw%2Fskew","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farpjw%2Fskew","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farpjw%2Fskew/lists"}