Metadata-Version: 2.4
Name: marina-rules
Version: 2026.10.26.3
Summary: Point-in-time trading rules for financial markets, as data. Price limits, fees, sessions, settlement — every record cites its source.
Author: David Li
License: MIT
Project-URL: Homepage, https://github.com/david-lihangyuan/marina-rules
Project-URL: Issues, https://github.com/david-lihangyuan/marina-rules/issues
Keywords: trading,backtest,exchange-rules,price-limit,fees,point-in-time,a-share
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: LICENSE-DATA
Requires-Dist: pyyaml>=6
Dynamic: license-file

# Marina

**Point-in-time trading rules for financial markets, as data.**

Price limits, fees, trading sessions, settlement — every record says *when* it took effect and *where* it was published. Any backtest or trading agent can cite the exact rules it ran under, and anyone can re-run it against byte-identical rules.

> **中文** · 市场交易规则的「tzdata」：涨跌幅、费用、tick 等制度**从哪天起、依据什么**，每条记录带官方原文、引文和内容哈希。给回测引擎和 AI Agent 用——查不到就报错，不猜；`coverage()` 预检整段窗口，`resolve()` 返回统一判定，`diff_rules` 告诉你升级后哪些回测要重跑。目前覆盖 A 股（含科创/创业/北交所、ST、IPO 窗口、退市整理期、ETF、REITs、可转债、B 股、T+1）。CC0 数据 + MIT 代码。

```
rules: marina-rules@2026.10.26.3 sha256:b7e1c4ec8d08d5e9
```

Marina is to trading rules what the [tz database](https://www.iana.org/time-zones) is to time zones and what [exchange_calendars](https://github.com/gerrymanoim/exchange_calendars) is to trading days: small, boring, free, and meant to be the one copy everyone imports instead of the thousand copies everyone gets slightly wrong.

> **Status: 2026.10.26.3.** China A-share price limits and transaction fees — **56 records, 50 `verified` + 6 `single-source`** against primary sources (exchange / ministry / depository notices, with quotes and page hashes). Other markets are not yet covered; see [Coverage](#coverage). Treat the exchange's own notice as authoritative, always.

## Why

Every backtest engine, every trading agent, and every research notebook re-implements the same handful of rules — "ChiNext limit is 20%" — and a large share of them get the *date* wrong (it was 10% before 2020-08-24), the *scope* wrong (ST stocks differ), or the *rounding* wrong (exchanges round half-up; `round()` doesn't). These errors used to live in one firm's code. Now that AI agents write backtests by the thousand, the same error gets copied by the thousand.

A shared, cited, versioned table fixes this the way tzdata fixed time zones: not by being authoritative, but by being **the same for everyone** and **traceable to the source**.

## Use

```bash
pip install marina-rules      # self-contained: the dataset ships inside the wheel, byte-pinned
# or take the JSON / SQLite dump attached to each GitHub Release — same bytes, same digest
```

```python
import marina_rules as mr

mr.rule_at("stamp_duty_sell", "2023-08-28")           # 0.0005
mr.rule_at("price_limit_pct", "2020-08-23", "chinext", "XSHE")   # 0.10
mr.limit_price(10.00, "chinext", "2021-03-01", "XSHE") # (12.0, 8.0)  — exchange rounding
mr.limit_price(10.00, "ipo_first5_main", "2024-01-30", "XSHG+XSHE")  # None — unlimited band
mr.rule_at("stamp_duty_sell", "2005-01-01")            # KeyError — no defaults, ever
mr.citation()                                          # 'rules: marina-rules@2026.10.25 sha256:…'

# Pre-flight a whole backtest window before running it:
mr.coverage("XSHE", "chinext", ["price_limit_pct"], "2019-01-01", "2026-10-19")
# {'status': 'complete', 'gaps': [], 'conflicts': [], 'verification': 'verified',
#  'required_context': ['board', 'st_status', 'listing_day_index'], 'excludes': [...], ...}
```

```bash
python -m marina_rules.diff 2026.10.24b 2026.10.25b      # what changed, and what must be re-run
```

`coverage()` answers "can Marina serve every (rule, day) in this window for this scope?" — `complete`,
`partial` (with the exact gaps), or `unsupported`. It also tells you what you must already know about
each security to select the scope legitimately (`required_context`): Marina holds rules, never a
security's ST or listing history, and it will not guess them for you. Gaps carry `known_gap_ids`
when they match [`data/cn/known_gaps.yaml`](data/cn/known_gaps.yaml) — the list of things Marina
knows it does not cover (T+1, sessions, REITs, pre-2022 convertibles…), each with
a reason and a status, so "unsupported" can be cited rather than worked around. Agents: read [SKILL.md](SKILL.md).

The data is plain YAML under [`data/`](data/). You do not need the Python package; read the files from any language.

### Cite it

Put `mr.citation()` in every result you publish. A result without it cannot be reproduced; a result with it can be re-run by anyone against the same bytes.

### Check a fill log

```bash
python -m marina_rules.check fills.csv [--strict]
```

Verifies each fill against the rules in force that day — no buys above limit-up, no sells below limit-down, fees not below the statutory floor — and emits a `rules-verified@<version>:<hash>` stamp when clean. The stamp is recomputable by anyone holding the same log and the same rules version.

**What the stamp does not mean:** it says nothing about look-ahead bias, survivorship, capacity, or statistical validity. It says only that the fills respected the rules in this dataset. Name it accordingly.

### Audit the rules against your own data

```bash
python scripts/audit_bars.py /path/to/daily_bars --complete-from-listing
```

A price-limit rule is falsifiable: after it takes effect, no close may move beyond the band. This script counts violations per rule. A violation means the rule is wrong or your data is — either way you want to know. Marina was first audited this way against 9.1 million A-share bars; see [`docs/audit-2026-10-24.md`](docs/audit-2026-10-24.md). The primary-source audit that followed, and the four corrections it produced, is in [`docs/sources-audit-2026-10-24.md`](docs/sources-audit-2026-10-24.md).

## Coverage

| Market | Price limits | Fees | Sessions / auctions | Tick & lot | Settlement | Margin |
|---|---|---|---|---|---|---|
| China A-share (XSHG, XSHE, BJSE) | ✅ 27 records (incl. B-share, ETF, convertible) | ✅ 6 records | — | — | — | n/a |
| China futures | — | — | — | — | — | — |
| US equities | — | — | — | — | — | — |
| Crypto | — | — | — | — | — | — |

Blank cells are blank on purpose. A missing rule raises `KeyError`; it does not return a guess.

## Scope — what Marina will and will not contain

**In:** rules that change *whether an order fills, at what price, or at what cost* — price limits, trading hours and auction windows, tick size and lot size, price cages, T+N settlement, statutory fees, exchange margin requirements, trading calendars (delegated to `exchange_calendars`).

**Out, permanently:** constituent lists, ST/suspension lists, borrow lists, prices, adjustment factors, anything with an owner or a license. Those are *data*; Marina holds *rules*. This boundary is what lets tzdata survive forty years without a legal department, and we are not going to be cleverer than tzdata.

## What the dates mean (and don't)

Each record carries three dates: `effective_from` (when the rule applied), `published_at` (when the
official notice was issued — `null` when only later restatements were located), and `recorded_at`
(when the record entered Marina). **`recorded_at` is not "known at".** Marina was assembled after the
fact; it does not by itself let you prove that a backtest avoided look-ahead on rule changes. If that
matters, use `published_at` and treat records with `published_at: null` as unknown-at-the-time.

## One answer shape for every language: the decision contract

```bash
python -m marina_rules.contract price_limit_pct 2024-06-14 st_main XSHE --context board=main_board,st_status=true
python -m marina_rules.contract --schema          # JSON Schema of the answer
python -m marina_rules.contract --capabilities    # what can be asked, generated from the data
```

`resolve()` returns one dict with `status ∈ {DETERMINED, MISSING_CONTEXT, OUT_OF_COVERAGE, CONFLICT,
UNVERIFIED}`, a `result` only when determined, the record and evidence refs, `known_gap_ids`, the
ruleset version and hash, and a `reason` in plain words. It is the boundary other languages, agents
and logs should see; the Python exceptions remain for in-process use.

## Upgrading: `diff_rules`

`python -m marina_rules.diff <from> <to>` lists every change between two tags (or directories) and
classifies each as **correction** (Marina was wrong about the past — results computed under the old
version on that interval were computed with a wrong rule), **regime_change** (the rule itself changed
on that date), **coverage_added**, **rename**, **bookkeeping** or **metadata**. It ends with the
`(market, scope, rule, interval)` list you must re-run. Both versions are pinned by dataset digest.
Marina does not estimate P&L impact; that is what the interval is for.

## Conformance vectors — check *your* implementation, in any language

[`conformance/vectors/`](conformance/) holds 88 input→expected pairs whose expected values come from
the exchange texts and hand arithmetic quoted in each vector's `oracle`, not from running Marina.
Regime switches one day before and on the day, ST vs non-ST, IPO windows, both exchanges' one-tick
floors, funds, convertibles, fees, missing and contradictory context, known gaps. Marina itself is
tested against them in CI; the first run caught a real defect. `python conformance/run.py`.

## Errata

When data contradicts a rule, so far the data has been wrong every time. [`errata/`](errata/) records each case with a sample and a one-command detector you can run on your own bars (stale pct-change columns; sub-¥1 adjusted-price rounding). Contributions welcome under the same bar: sample + detector + the fact that settles it.

## Verification levels

Every record carries one of three levels, in the data, in the open:

| level | meaning |
|---|---|
| `verified` | ≥1 official primary source (exchange / ministry / depository) states this value and date, **or** ≥2 independent authoritative sources agree. Every source carries a verbatim `quote` and a content hash of the fetched document (text for HTML, bytes for PDF) — mandatory, enforced at load time |
| `single-source` | one source, or an official source where date or value had to be inferred |
| `unverified` | secondary sources only, or sources disagree (the disagreement is recorded in `notes`) |

`strict=True` in the API refuses `unverified` records. Release tags include all levels; the level is part of the record, not hidden in a changelog.

## Contributing

Yes, please — this only works as a commons. One record per pull request, with the primary source linked. CI checks the schema, the sources, interval overlap, and runs the audit on the sample bars. See [CONTRIBUTING.md](CONTRIBUTING.md) and [GOVERNANCE.md](GOVERNANCE.md).

## Acknowledgements

The China A-share timeline was cross-checked against the rule histories compiled in the MIT-licensed PyPI package [`data-backtest-live-common`](https://pypi.org/project/data-backtest-live-common/) (2026-09), whose anonymous author did careful work on the fee and limit chronology, and against [`exchange_calendars`](https://github.com/gerrymanoim/exchange_calendars) for calendar conventions. If you are the dbl_common maintainer: please open an issue; this table should have your name on it.

## License

Data (`data/`, `docs/`): [CC0 1.0](LICENSE-DATA) — no rights reserved.
Code (`src/`, `scripts/`, `tests/`): [MIT](LICENSE).

## About the name

Marina is named after my wife, Marina. This project was created and first published on **24 October 2026**, her birthday.

A rules table is an unglamorous thing: a few hundred lines of dates, percentages, and citations. But every one of those lines is a mistake that someone, somewhere, no longer has to make. If this table spares even a few people a wrong number, a bad trade, or a wasted week, then it has done what I hoped it would do on the day it was named — bring a little more good into the world, in her name.

It is free for everyone, forever. No rights reserved.

— David Li, 24 October 2026

---

**关于名字**

Marina 以我爱人 Marina 的名字命名。这个项目建立并首次发布于 **2026 年 10 月 24 日**，她的生日。

一张规则表是件很不起眼的东西：几百行日期、百分比和出处。但其中每一行，都是某个人在某个地方不必再犯的一个错误。如果这张表能让哪怕几个人少写错一个数字、少做错一笔交易、少白费一周时间，它就做到了命名那天我希望它做到的事——以她的名字，给这个世界多带来一点好。

它对所有人免费，永远如此。不保留任何权利。
