Writing these docs¶
This site is built with MkDocs and the Material theme. Its hand-written pages live in docs/, and its API reference is generated from the docstrings in src/tradingmachine every time the site is built. Docs live with the code and change in the same commit as it.
The animation below shows how the parts come together in one build.
Previewing and building¶
The documentation toolchain is the docs extra in pyproject.toml, so installing the library with that extra puts it in the project's virtual environment. The commands below install it and then cover almost everything else, all run from the project root.
.venv/bin/python -m pip install -e ".[docs,development]"
.venv/bin/mkdocs serve # live preview on http://127.0.0.1:8000, rebuilt on every save
.venv/bin/mkdocs build --strict # the full build, failing on any warning
mkdocs serve also watches src (the watch: list in mkdocs.yml), so editing a docstring refreshes the API reference too.
Always finish with mkdocs build --strict. In strict mode every warning is an error, so a broken link, a page missing from the nav, a missing snippet file or an unresolvable code cross-reference stops the build. Filtering its output shows only what needs fixing:
MkDocs also logs one line at INFO level on every build, Doc file 'index.md' contains an unrecognized relative link 'reference/', because that link points at a directory the generator creates rather than at a file on disk. It does not fail a strict build and can be ignored.
Publishing¶
The site is published on GitHub Pages at https://pramodathani.github.io/tradingmachine/. The GitHub Actions workflow .github/workflows/docs.yml builds it with mkdocs build --strict and deploys it, so nobody publishes by hand. The table below shows what the workflow does for each kind of event.
| Event | Builds and checks the site | Publishes it |
|---|---|---|
A push to main, which includes merging a pull request |
||
| A pull request | ||
| A manual run from the Actions tab | Only on main |
The flowchart below shows the same thing as a pipeline.
flowchart LR
PR["Pull request"] --> B["build job<br/>mkdocs build --strict"]
M["Push to main"] --> B
B -->|"pull request"| X["stop: check only"]
B -->|"main"| D["deploy job<br/>actions/deploy-pages"]
D --> S["pramodathani.github.io/<br/>tradingmachine"]
The workflow installs only the eight packages in the docs extra. It reads that list out of pyproject.toml with Python's tomllib and installs it with pip, rather than running pip install ".[docs]", which would install the library and its dependencies too. One of those dependencies is TA-Lib, a wrapper around a native C library that GitHub's runner does not have, so that install would fail. The site does not need the library installed, because mkdocstrings reads the files under src without importing them, and the workflow needs no database, no .env, no UBI and no credentials. A pull request that breaks a link or a code reference fails its check before it can be merged, but running the strict build locally before pushing still saves a round trip.
The generated site/ directory is ignored by git and never belongs on a branch.
The configuration¶
mkdocs.yml holds the whole configuration. The table below lists the plugins it loads, the version of each pinned in the docs extra of pyproject.toml, and what each one does for this site.
| Plugin | Package and pinned version | What it does here |
|---|---|---|
search |
built into MkDocs, mkdocs==1.6.1 |
The search box |
charts |
mkdocs-charts-plugin==0.0.13 |
Renders ```vegalite fences as Vega-Lite charts, with the default theme in light mode and dark in dark mode |
gen-files |
mkdocs-gen-files==0.5.0 |
Runs scripts/gen_ref_pages.py at build time to create one reference page per module |
literate-nav |
mkdocs-literate-nav==0.6.2 |
Reads reference/SUMMARY.md, which the generator writes, as the navigation of the API reference |
section-index |
mkdocs-section-index==0.3.9 |
Lets a section's index.md be the page you land on when you click the section |
mkdocstrings |
mkdocstrings[python]==0.29.0 |
Turns each dotted.path line into documentation read from the docstrings, Google style |
The theme is mkdocs-material==9.6.9, with deep orange as its primary and accent colour, the material/chart-line logo, and a light, dark and "follow the system" toggle. The Markdown extensions come from pymdown-extensions==10.21.3. The ones this site relies on are admonition and pymdownx.details for the boxes, pymdownx.superfences for the Mermaid and Vega-Lite fences, pymdownx.tabbed for content tabs, pymdownx.snippets for including the SVG files, pymdownx.emoji for the Material icons, and attr_list and md_in_html for the cards, member badges and diagrams.
How the API reference is generated¶
scripts/gen_ref_pages.py holds one class, ReferencePageBuilder, which the gen-files plugin runs by path. It walks every .py file under src/tradingmachine, and the steps below are what it does for each file.
- It skips any path containing
__pycache__. - It turns the file path, relative to
src, into a dotted module path and a page path underreference/. A package's__init__.pybecomes that package'sindex.md, and an empty__init__.pyis skipped because it has nothing to show. Today that skips theassets,assets.analysis,unified_broker_interfaceandutilitiespackages, so onlytradingmachine,tradingmachine.accounts,tradingmachine.asset_basketsandtradingmachine.ordersget an index page. Any other dunder module, such as a__main__.py, is skipped too. A module that is itself namedindex, such astradingmachine.asset_baskets.index, is written toindex_module.md, becauseindex.mdin its folder already belongs to the package; two entries sharing one page made the section-index plugin loop forever. - It writes the page in memory, not on disk, as a hierarchy. The module comes first as the page title, through
tradingmachine.assets.equitieswithheading_level: 1and only the module's constants as members. Then each public class follows in the order the module defines it, through its owntradingmachine.assets.equities.Equityline withheading_level: 2, so its properties and methods become third-level headings in source order and the menu on the right lists every class with its members beneath it. After each class it adds that class's example programs, described below. - It records an edit link back to the source file, such as
src/tradingmachine/assets/equities.py, and adds the page to the navigation. - At the end it writes
reference/index.md, the class hierarchy, a tree of all the library's classes by inheritance with each one linked to its section, and thenreference/SUMMARY.md, which literate-nav reads, with the class hierarchy as the section's first entry. That is why the nav inmkdocs.ymlsays only- API reference: reference/.
A new module therefore appears in the reference with no edits anywhere. The builder keeps two roots on purpose: the repository root for edit links, and src for the dotted path, so that the identifier reads tradingmachine.assets.equities rather than src.tradingmachine.assets.equities. The script sits in scripts/ rather than in the package, so it is never shipped to anyone installing the library.
The mkdocstrings options in mkdocs.yml matter when you write docstrings. The table below lists the ones that change what appears.
| Option | Value | Effect |
|---|---|---|
paths |
[src] |
Identifiers resolve against the source tree, so the build does not depend on the library being installed |
docstring_style |
google |
Args:, Returns: and Raises: sections are parsed into tables |
filters |
["!^_[^_]"] |
Every name that starts with one underscore is hidden |
show_if_no_docstring |
false |
Anything without a docstring is hidden |
inherited_members |
false |
A class page does not repeat what it inherits, as explained below |
returns_named_value |
false |
Returns: A pandas.DataFrame ... is read as a description, not as a value named "A" |
merge_init_into_class |
true |
The constructor's arguments appear on the class |
members_order |
source |
Members appear in the order the code defines them |
group_by_category |
false |
Properties and methods are not gathered into separate groups, so members_order: source holds across both |
Examples in docstrings and example programs¶
Every public property and method ends its docstring with an Examples: section holding two or three fenced python code blocks, each introduced by one sentence ending in a colon. Each block is complete on its own, with its own imports, because the runner described below runs every block by itself. The reference page renders the section under the member, as it does Args: and Returns:.
Every public class also has at least two example programs under examples/, in a folder that mirrors the module path without the leading tradingmachine and is named after the class in snake case. The table below shows how the folder is found.
| Class | Folder of its programs |
|---|---|
tradingmachine.accounts.account.Account |
examples/accounts/account/account/ |
tradingmachine.assets.equities.EquityIndexOption |
examples/assets/equities/equity_index_option/ |
tradingmachine.assets.exceptions.HoldingError |
examples/assets/exceptions/holding_error/ |
A program is a runnable script written in the project's own style: a module docstring whose first line is the program's title, one class with a run method, and an if __name__ == "__main__": guard that only creates the class and calls run. scripts/gen_ref_pages.py finds the programs by that folder, shows each one under its class with the title as a heading, and includes the file itself through pymdownx.snippets, so the page always shows the program exactly as it is on disk. Adding a program therefore needs no edit anywhere else.
scripts/run_examples.py runs every docstring code block and every program against the live UBI, one at a time, and prints passed, failed, timed out or held back for each. The commands below run all of them, one module, or one class's programs.
.venv/bin/python scripts/run_examples.py
.venv/bin/python scripts/run_examples.py --only tradingmachine.assets.equities
.venv/bin/python scripts/run_examples.py --only examples/assets/equities/equity/ --show-output
The examples place real orders
Many examples place, change and cancel real orders at real brokers, because the project's rule is that its examples are verified for real. Each one cleans up what it creates, but UBI chooses the broker for every order, so a round trip can leave one intraday share long at one broker and short at another, which the brokers square off at the close. Run the order examples during market hours only when that is acceptable. Any example that calls flatten, liquidate_all_positions, add_to_holdings, reduce_holdings, liquidate_holdings or rebalance is held back unless --include-account-wide is given, because it acts on positions or holdings that were there before it ran.
What the build hook does¶
scripts/documentation_hooks.py exists to stop one griffe warning from failing every strict build. Every docstring in this project has a Raises: section, and a member that raises nothing says so with the single word Nothing., which griffe's Google parser does not recognise as an exception: description pair. It warns Failed to get 'exception: description' pair from 'Nothing.', and --strict would turn each such warning into a failure.
MkDocs imports the file because mkdocs.yml lists it under hooks:, and calls its on_config function when the configuration is loaded. on_config creates a DocumentationHooks object, whose install method adds a NothingRaisedFilter to the handlers of the mkdocs logger and the root logger. The filter drops that one message, matched by its text, and nothing else. The filter goes on the handlers rather than on a logger, because griffe's messages reach MkDocs by propagating up from a logger mkdocstrings names, and a filter on a logger only sees records logged through that logger. A deliberately broken cross-reference still fails the build with the hook installed.
Why inherited_members is false¶
Instrument inherits fourteen analysis classes, 209 methods, and all 27 family classes inherit from it; when the measurement below was taken it was thirteen classes and about 190 methods. With inherited_members: true, which the sibling UBI site uses, every family class reprinted the whole analysis surface on its own reference page. The table below shows what that did, measured on 2026-09-20 on the same content.
inherited_members |
Whole site | Build time | Largest page |
|---|---|---|---|
true |
151 MB | 112 seconds | 23.8 MB |
false |
13 MB | 5.9 seconds | 1.0 MB |
A 24 MB HTML page is not usable in a browser, so the setting is off. The analysis methods are still documented once each, on the reference pages of the modules that define them, and each class page names its base classes. Design choices records the decision.
The four asset family pages are the one exception. scripts/gen_ref_pages.py gives them an inherited_members list naming expiries, contracts, strikes and chain, which the futures and option classes inherit from the derivative bases, so those four calls still show on each family class's page and nothing else inherited does.
Adding a page¶
A new narrative page takes two steps: create the Markdown file under docs/, and add it to nav in mkdocs.yml. A section's landing page is the index.md of its folder and is listed without a title, like - project/index.md. The API reference needs no nav entry, because it is generated.
Every page under docs/python-api/ follows one template, modelled on Zerodha's Kite Connect documentation, so a reader always finds the same things in the same place. The flowchart below shows the order of its parts.
flowchart TB
T["# Title<br/>one or two sentences"] --> S["Summary table<br/>Kind | Member | Description"]
S --> G["Glossary of plain strings<br/>or a link to Vocabulary"]
G --> M["## One heading per member"]
M --> M1["Member header block<br/>badge, signature, UBI route"]
M1 --> M2["#### Parameters"]
M2 --> M3["#### Example<br/>Python and Output tabs"]
M3 --> M4["#### Returns"]
M4 --> M5["#### Raises"]
M5 --> M6["??? note Under the hood"]
A page whose members place orders opens with a !!! danger "These are real orders" box. The level-4 headings keep the per-member subheadings out of the right-hand table of contents.
Links¶
Links to other pages use relative .md paths, which MkDocs checks at build time. A heading's anchor is its text in lower case, with spaces turned into hyphens and underscores kept, so the heading place_order has the anchor #place_order.
See [place_order](../python-api/orders.md#place_order) and [Equities](../asset-classes/equities.md).
Links to code use mkdocstrings cross-references, which point into the generated reference. The target must be public, with no leading underscore, and have a docstring, or the strict build fails. This page links to Equity with this markup:
When you are not sure a name qualifies, cite the file path in backticks instead, such as src/tradingmachine/assets/instruments.py.
Links to UBI's behaviour go to its published site with absolute URLs under https://pramodathani.github.io/unified_broker_interface/, such as Order engine. Link to UBI rather than repeating its documentation here, and check an anchor against the heading in UBI's docs/rest-api/*.md before using it.
Visual conventions¶
Every page aims for at least one diagram or chart, and uses tables, lists and code blocks wherever the content has that shape. The table below lists every visual element this site uses and how to write it.
| Element | Use it for | How |
|---|---|---|
| Table | Anything with repeating fields | Markdown table, with a full sentence before it |
| Mermaid diagram | Flows, sequences, class hierarchies, state machines | ```mermaid fence; a sequenceDiagram starts with autonumber |
| Vega-Lite chart | Real numbers from the code or from a measurement | ```vegalite fence holding a JSON spec |
| Animated SVG | The site's headline diagrams, with moving dots | An .svg file in docs/assets/diagrams/, included with a snippet |
| Content tabs | The same thing in several forms, such as Python and its output | === "Python" with the content indented four spaces |
| Admonition | Warnings, tips and notes | !!! danger, !!! warning, !!! tip, !!! note, ??? note "Under the hood" |
| Cards | Section landing pages | <div class="grid cards" markdown> |
| Member badge | What kind of member something is | <span class="member property">property</span> and the four others below |
| Member header | The top of each member's section | <div class="endpoint" markdown>…</div> |
| HTTP method badge | The UBI route a member calls | <span class="method get">GET</span> |
| Status chip | An HTTP status code | <span class="status s2">200</span> |
| Icon | A tick, cross or dash in a table | :material-check:, :material-close:, :material-minus: |
The custom classes all live in docs/stylesheets/extra.css. Use only the classes that exist there.
Member badges¶
A member badge says what kind of member a name is, and its colour warns before its name does. The table below shows each badge, its markup and when to use it.
| Badge | Markup | Use it for |
|---|---|---|
| property | <span class="member property">property</span> |
A member that only reads a value, written without brackets |
| method | <span class="member method">method</span> |
A member that takes arguments and only reads |
| classmethod | <span class="member function">classmethod</span> |
A class method used for discovery, such as search or chain |
| places orders | <span class="member writes">places orders</span> |
A member that sends, changes or cancels real orders |
| class | <span class="member class">class</span> |
A class you construct |
Member headers, method badges and status chips¶
A member header puts the badge, the signature and the UBI route it calls in one bar. It must be written on one line, with markdown on the div so the backticks inside it still work:
<div class="endpoint" markdown><span class="member method">method</span> `prices(interval="day", from_date=None, to_date=None, days=None, adjusted=True)`<span class="route"><span class="method get">GET</span> `/api/instruments/prices`</span></div>
method prices(interval="day", from_date=None, to_date=None, days=None, adjusted=True)GET /api/instruments/prices
A method badge colours an HTTP method: get is green, post blue, put amber and delete red. UBI has no PATCH route, and a PATCH would use put.
GET POST PUT DELETE
A status chip colours a status code by its class: s2 is green for success, s4 amber for a client error and s5 red for a server error.
200 409 504
Animated SVG¶
The animated diagrams are hand-written SVG files in docs/assets/diagrams/, styled entirely by the classes in extra.css, so they follow the light and dark theme without any colours of their own. The table below lists those classes.
| Class | Draws |
|---|---|
tm-svg |
The root <svg> element, required for every other class to apply |
box |
An ordinary box |
store |
A data store, in a bluish fill |
accent-box |
The box the diagram is about, with an orange border |
wire |
A dashed connector that marches, unless the reader prefers reduced motion |
wire-solid |
A still connector |
title, small, mono |
Bold, muted and code-font text |
dot, dot alt, dot ok |
Moving dots in orange, blue and green, hidden when the reader prefers reduced motion |
A dot moves along a path with <animateMotion> and <mpath>. Every id in a file starts with a prefix unique to that file, so two diagrams on one page cannot clash. This is one connector and its dot from docs-build.svg, the diagram at the top of this page:
<path id="tm-docs-build-output" class="wire" d="M710,160 L760,160"/>
<circle class="dot ok" r="6"><animateMotion dur="1s" repeatCount="indefinite"><mpath href="#tm-docs-build-output"/></animateMotion></circle>
A dot that should wait, so that a request and its answer take turns, uses calcMode="linear" with keyTimes and keyPoints, and an <animate> on its opacity to hide it while it waits; held-limit-order.svg on Order engine works this way. Draw the dots before the boxes, so a dot passing behind a box is hidden rather than drawn over its text.
Each SVG also carries a <title> and a <desc> for screen readers, and has no blank lines inside it, because a blank line would end the HTML block when the file is pasted into the page. A page includes it with the snippets extension, inside a figure with a one-sentence caption, and with no blank lines between the four lines:
<figure class="diagram">
--8<-- "docs/assets/diagrams/docs-build.svg"
<figcaption>One sentence explaining what each dot colour means.</figcaption>
</figure>
Mermaid and Vega-Lite¶
Mermaid diagrams are written as text in a fenced block, and the theme draws them. Keep node labels short, use <br/> for a line break, and never put a semicolon inside label text, because it breaks Mermaid's parser.
A chart is a Vega-Lite JSON spec in a vegalite fence. Use the v5 schema, "width": "container" so it fills the column, and inline data in "data": {"values": [...]}. Chart only real numbers, from the code or from a measurement that the page dates. Repository structure has an example.
Admonitions¶
Admonitions are the coloured boxes. Each type has one job, and danger is reserved.
| Type | Use it for |
|---|---|
!!! danger |
Only for something that places real orders |
!!! warning |
A trap that costs time or gives a wrong answer |
!!! tip |
A shortcut or a better way |
!!! note |
Background worth knowing |
??? note "Under the hood" |
A collapsed box with the UBI request body, the private methods and the files behind a member |
Writing style¶
Every page follows the same writing rules, so the site reads as one voice. The list below collects them.
- Write in simple language and complete sentences. Every sentence has a subject and a verb, and a heading never stands in for the sentence that introduces a section.
- Name a thing in plain words before, or instead of, its identifier in the code.
- Put a complete sentence before every table, diagram, list and code block saying what it shows. Cells and list items can be short; the prose around them cannot.
- Keep facts exact. Never invent a value, field name, status code or message. Real output is copied, never reconstructed, and it says where and when it was captured.
- Replace every broker account identifier in captured output with a placeholder such as
XX000000, and say in a sentence when output was trimmed.