Metadata-Version: 2.4
Name: opencontractid
Version: 0.1.0
Summary: OpenContractID (OCID): deterministic, reversible UUIDv8 identifiers for financial instruments and contracts.
Author: OpenContractID Contributors
License: Apache-2.0
Project-URL: Homepage, https://github.com/opencontractid/opencontractid
Project-URL: Documentation, https://github.com/opencontractid/opencontractid/tree/main/docs
Project-URL: Repository, https://github.com/opencontractid/opencontractid
Project-URL: Issues, https://github.com/opencontractid/opencontractid/issues
Keywords: finance,uuid,uuidv8,symbology,options,contracts,instruments
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: twine>=5.1; extra == "dev"
Dynamic: license-file

# OpenContractID (OCID)

OpenContractID is an open specification and Python reference implementation for **deterministic, reversible, UUIDv8-based identifiers** for financial instruments and contracts.

OCID treats the identifier itself as the stable identity. A broker ID, database sequence, FIGI, ISIN, Yahoo symbol, IBKR `conId`, or other provider identifier is an alias or metadata rather than the source of truth.

## Current status

This repository is the **unreleased initial implementation**. The package version remains `0.1.0` until the first publication. The protocol is explicitly versioned by an OCID schema nibble so future incompatible layouts can coexist.

## Design

Two public value types define the Python API:

- `Contract`: immutable domain value object and human-readable representation.
- `ContractUUID`: a subclass of Python's `uuid.UUID`, carrying the deterministic OCID identity.

`Contract` is not a persistence entity. No `contracts` table is required to recover the core identity fields.

```text
Contract
   │ encode
   ▼
ContractUUID (uuid.UUID subclass)
   │ decode
   ▼
Contract
```

For options, `print()`, `str()` and `repr()` use OCC/OSI 21-character option symbology. `ContractUUID` deliberately retains normal UUID string behavior.

## Schema 1 payload

OCID uses UUIDv8 as a 128-bit container. UUID version and variant consume 6 fixed bits. The remaining 122 payload bits are:

| Field | Bits | Schema 1 meaning |
|---|---:|---|
| schema | 4 | OCID schema version (`1`) |
| market | 8 | canonical market namespace |
| asset_type | 4 | equity / ETF / option / ... |
| symbol | 54 | reversible symbol code, max 10 canonical chars |
| expiry | 16 | days since `2000-01-01`; zero means none |
| right | 2 | none / call / put |
| strike | 30 | fixed-point `strike * 1000` |
| reserved | 4 | zero in Schema 1 |

Canonical symbol alphabet:

```text
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-
```

## Install

Development checkout:

```bash
python -m pip install -e '.[dev]'
```

After the package is published:

```bash
pip install opencontractid
```

## Python API

### Create an option

`strike` accepts either `Decimal` or a numeric string. Floating-point strikes are intentionally rejected.

```python
from decimal import Decimal

from ocid import Contract

contract = Contract.option(
    "INTC",
    market="US",
    expiry="2026-08-21",
    right="C",
    strike="150",
)

print(contract)
# INTC  260821C00150000

assert contract.strike == Decimal("150")
```

Equivalent construction with `Decimal`:

```python
contract = Contract.option(
    "INTC",
    expiry="2026-08-21",
    right="CALL",
    strike=Decimal("150.125"),
)
```

### Convert to UUID

```python
from uuid import UUID

cid = contract.to_uuid()

assert isinstance(cid, UUID)
print(cid)
# standard UUID text, UUID version 8
```

`ContractUUID` extends Python's standard `uuid.UUID`, so it can generally be passed directly to PostgreSQL drivers, SQLAlchemy UUID columns, Pydantic UUID fields and APIs expecting a UUID.

Aliases are provided where external SDK conventions make them convenient:

```python
contract.to_uuid()
contract.toUUID()
contract.to_id()
contract.toID()
contract.id
contract.uuid
```

### Decode without a registry or database

```python
from ocid import Contract

restored = Contract.from_uuid(cid)
assert restored == contract
```

Accepted OCID inputs include `ContractUUID`, `uuid.UUID`, UUID string, 16-byte UUID bytes and integer UUID values through `ContractUUID.parse()`.

```python
Contract.from_uuid(cid)
Contract.fromUUID(cid)
Contract.from_id(str(cid))
Contract.fromID(str(cid))

cid.to_contract()
cid.toContract()
```

### OCC / OSI

Options render as the OCC/OSI 21-character format:

```python
contract = Contract.from_osi("INTC  260821C00150000")

str(contract)
# 'INTC  260821C00150000'

repr(contract)
# 'INTC  260821C00150000'

contract.to_osi()
# 'INTC  260821C00150000'
```

OSI conversion is a human/exchange representation. OCID remains the identity. `str(contract.id)` always remains standard UUID text.

### Equities

```python
from ocid import Contract

intel = Contract.equity("intc", market="US")

str(intel)
# 'INTC'

intel.id
# ContractUUID(...)
```

US share/class separators are normalized into the OCID canonical form:

```python
Contract.equity("BRK-B").symbol
# 'BRK.B'
```

### Numeric markets

Market decorators normalize exchange conventions before encoding:

```python
Contract.equity("700", market="HK").symbol
# '00700'

Contract.equity("1", market="CN").symbol
# '000001'
```

## Market decorators

The UUID layout is global. Markets normally customize only canonicalization and validation.

```python
from ocid import Market, contract_market

@contract_market(Market.US)
class USMarketRules:
    @staticmethod
    def normalize_symbol(symbol: str) -> str:
        return symbol.strip().upper().replace("-", ".")
```

Do not create a separate UUID codec for every exchange. A market decorator should adapt its symbology into the global canonical contract model.

Built-in namespaces currently include US, HK, CN, JP, GB, DE, FR, NL, CH, CA, AU, SG, GLOBAL, FX and CRYPTO.

## CLI

Encode:

```bash
ocid encode \
  --market US \
  --asset OPTION \
  --symbol INTC \
  --expiry 2026-08-21 \
  --right CALL \
  --strike 150
```

Decode:

```bash
ocid decode <uuid>
```

Parse OSI and emit OCID:

```bash
ocid osi 'INTC  260821C00150000'
```

## Provider identity

Keep provider identifiers outside the core identity:

```text
OCID / ContractUUID   deterministic internal identity
FIGI / ISIN           external industry identity
IBKR conId            broker identity
Yahoo/Futu symbol     provider alias
exchange              metadata / venue
```

A persistence system may store queryable metadata keyed by OCID, but the metadata record does not own the identity.

## Agent skills

The repository includes skills under `skills/`:

- `opencontractid-python-api/SKILL.md`: how an agent should consume OCID from Python applications.
- `opencontractid-development/SKILL.md`: how an agent should safely modify the implementation and protocol.

The Python API skill is deliberately usage-oriented: construction, conversion, parsing, persistence boundaries, safe strike handling and provider integration.

## Repository layout

```text
src/ocid/
  model.py          Contract + enums
  uuid8.py          ContractUUID + UUIDv8 free-bit mapping
  codec.py          Schema 1 packing and validation
  symbol_codec.py   reversible symbol encoding
  registry.py       decorator-based market registration
  markets/          market canonicalization rules
  cli.py            command-line interface

docs/SPEC.md        protocol specification
docs/ARCHITECTURE.md domain and integration architecture
skills/              Python API and development agent skills
tests/               behavior and protocol tests
AGENTS.md            coding-agent repository rules
```

## Development and release

```bash
python -m pip install -e '.[dev]'
pytest
ruff check .
mypy src/ocid
python -m build
python -m twine check dist/*
```

The package is configured for PyPI as `opencontractid` and imports as `ocid`. The first production publication should occur only after the Schema 1 golden vectors are intentionally frozen.

## Intentional Schema 1 constraints

- Canonical symbol: at most 10 characters from `A-Z0-9.-`.
- Option strike input: `Decimal` or numeric `str`; no float.
- Strike precision: at most `0.001`.
- OCID encoded strike maximum: `(2^30 - 1) / 1000`.
- OCC/OSI rendering additionally requires a root that fits 6 characters and an 8-digit scaled strike.
- Expiry: uint16 day offset from `2000-01-01` with zero reserved for no expiry.
- Corporate-action renames produce a new symbol-derived identity in Schema 1; metadata can link identities.
- Complex adjusted options, exotic derivatives and exceptional long symbols are deferred.

## License

Apache-2.0. See `LICENSE`.
