Metadata-Version: 2.4
Name: tossinvest
Version: 0.1.0
Summary: Python client for the Toss Securities (토스증권) Open API
Project-URL: Homepage, https://suhjae.dev
Project-URL: Repository, https://github.com/SuhJae/tossinvest-py
Project-URL: Changelog, https://github.com/SuhJae/tossinvest-py/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/SuhJae/tossinvest-py/issues
Author-email: SuhJae <j@suhjae.dev>
License: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: brokerage,stocks,tossinvest,trading
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Description-Content-Type: text/markdown

# tossinvest

Async Python client for the **Toss Securities (토스증권) Open API** — a faithful port of
[`tossinvest-rust`](https://github.com/SuhJae/tossinvest-rust).

- Pure REST client with OAuth2 `client_credentials` auth
- Exact decimals (`Decimal`, never `float`), unknown-tolerant open enums, status-split envelope decoding
- Per-group rate limiting (GCRA + adaptive AIMD) with the 09:00–09:10 KST peak-window awareness
- Account-scoped client handle so the `X-Tossinvest-Account` header is unforgettable

> Status: alpha. Stateless SDK (model → rate → client) **and** the stateful/observable
> layer (`StateHandle`) are implemented.

## Install

```bash
pip install tossinvest
```

## Quickstart

```python
import asyncio
from tossinvest import TossClient, ClientConfig

async def main():
    async with TossClient(ClientConfig(client_id="...", client_secret="...")) as client:
        prices = await client.prices(["005930"])
        price = prices[0]
        print(price.last_price, price.currency)

asyncio.run(main())
```

Response models live in `tossinvest.model` (e.g. `from tossinvest.model import PriceResponse`).

## Stateful layer (`StateHandle`)

The L4/L5 reconciler maintains a live, single-writer projection of your orders,
holdings, and prices, with optimistic mutations and on-demand refresh:

```python
import asyncio
from tossinvest import TossClient, ClientConfig
from tossinvest.state import StateHandle

async def main():
    async with TossClient(ClientConfig(client_id="...", client_secret="...")) as client:
        account = client.account(seq)
        async with await StateHandle.spawn(account) as handle:
            async with handle.watch_prices(["005930"]):   # demand lease
                snap = handle.snapshot()                   # wait-free read
                print(snap.prices.get("005930"))

asyncio.run(main())
```

## License

Apache-2.0
