Metadata-Version: 2.5
Name: stonepy
Version: 0.4.1
Summary: Python client for the StoneX (CIAPI) v2 trading API
Project-URL: Homepage, https://github.com/aaronmgn/stonepy
Project-URL: Repository, https://github.com/aaronmgn/stonepy
Project-URL: Documentation, https://aaronmgn.github.io/stonepy/
Project-URL: Issues, https://github.com/aaronmgn/stonepy/issues
Project-URL: Changelog, https://github.com/aaronmgn/stonepy/blob/main/CHANGELOG.md
Author-email: Aaron Morgan <aaron@dvops.io>
License-Expression: MIT
License-File: LICENSE
Keywords: ciapi,cityindex,stonex,trading
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: pydantic<3.0,>=2.7
Requires-Dist: simplejson<5.0,>=3.19
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pytest-cov>=5; extra == 'dev'
Requires-Dist: pytest>=9.1.1; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Requires-Dist: types-simplejson; extra == 'dev'
Requires-Dist: unasync>=0.6; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mike>=2.1; extra == 'docs'
Requires-Dist: mkdocs-gen-files<0.7,>=0.5; extra == 'docs'
Requires-Dist: mkdocs-literate-nav<0.6.4,>=0.6; extra == 'docs'
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.27; extra == 'docs'
Requires-Dist: ruff>=0.5; extra == 'docs'
Description-Content-Type: text/markdown

# stonepy

[![PyPI version](https://img.shields.io/pypi/v/stonepy.svg)](https://pypi.org/project/stonepy/)
[![Python versions](https://img.shields.io/pypi/pyversions/stonepy.svg)](https://pypi.org/project/stonepy/)
[![CI](https://github.com/aaronmgn/stonepy/actions/workflows/ci.yml/badge.svg)](https://github.com/aaronmgn/stonepy/actions/workflows/ci.yml)
[![Docs](https://img.shields.io/badge/docs-online-blue.svg)](https://aaronmgn.github.io/stonepy/)
[![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](https://github.com/aaronmgn/stonepy/blob/main/LICENSE)

Python client for the StoneX (CIAPI) v2 trading API.

📖 **Documentation:** <https://aaronmgn.github.io/stonepy/>

## Features

- **Fully typed.** Request and response DTO bodies are
  [Pydantic](https://docs.pydantic.dev/) v2 models; some methods take primitive parameters or
  return bare scalars or lists. The package ships a `py.typed` marker, so editors autocomplete
  fields and `mypy` checks your calls.
- **Sync and async.** Identical APIs on `StoneXClient` and `AsyncStoneXClient`.
- **Complete coverage.** All 128 documented CIAPI endpoints across 19 resource groups, using the
  v2 variant of every endpoint that has one.
- **Batteries included.** Automatic session refresh, configurable retries, client-side rate
  limiting, masking for app key, password, session, authorization, and proxy values in
  client-owned object representations, and a clear exception hierarchy.

> **Project status:** `stonepy` is pre-1.0 (alpha). The public API may change between minor
> releases until 1.0; pin a version for production use.

## Installation

```bash
pip install stonepy
```

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv add stonepy
```

Requires Python >= 3.11. `stonepy` ships type information (PEP 561 `py.typed`), so it works out
of the box with `mypy` and `pyright`.

## Quickstart

```python
from stonepy import ClientConfig, StoneXClient
from stonepy.models import ApiLogOnRequestDTO

config = ClientConfig(base_url="https://ciapi.cityindex.com/TradingAPI")

with StoneXClient(config) as client:
    session = client.session.log_on(
        ApiLogOnRequestDTO(
            UserName="username",
            Password="password",
            AppKey="app-key",
            AppVersion="stonepy",
            AppComments="",
        )
    )
    print(session.status_code)
```

Environment-based configuration is also available:

```python
from stonepy import ClientConfig, StoneXClient

config = ClientConfig.from_env()

with StoneXClient(config) as client:
    print(client.session)
```

`ClientConfig.from_env()` reads `STONEX_BASE_URL`, `STONEX_APP_KEY`, `STONEX_USERNAME`,
and `STONEX_PASSWORD`. `STONEX_BASE_URL` is required unless `base_url=` is passed.

## Authentication and Sessions

Calling `client.session.log_on(...)` establishes the authenticated session token. The client
attaches the current token to subsequent endpoints that use session authentication. Refresh can
replace the token, and `client.session.delete_session(...)` clears it.

Automatic refresh is enabled either by supplying `app_key`, `username`, and `password` on
`ClientConfig` (directly or via `ClientConfig.from_env()`), or by a successful manual `log_on()`,
which installs a replay refresh callable. Proactive refresh runs inline, immediately before the
request that needs it - synchronously in `StoneXClient` and awaited in `AsyncStoneXClient`, with
no background task - once the stored token reaches `ClientConfig.proactive_refresh_seconds`
(default `1080.0`, i.e. 18 minutes). This threshold is based on token age; no server expiry
timestamp is consulted.

After HTTP `401` or `ErrorCode` `4011` (never `4010`) on an authenticated endpoint, the client
refreshes the session once and replays the request once. This applies to every endpoint,
including non-idempotent order calls, because this authentication rejection means the server did
not process the request. Transport, `5xx`, and `429` retries remain idempotency-gated. If
proactive refresh fails with a stonepy error, the client logs a warning and tries the request
with the existing token so reactive authentication recovery can still run.

```python
config = ClientConfig(
    base_url="https://ciapi.cityindex.com/TradingAPI",
    app_key="app-key",
    username="username",
    password="password",
)  # credentials present -> automatic proactive session refresh
```

## Async Usage

```python
from stonepy import AsyncStoneXClient, ClientConfig
from stonepy.models import ApiLogOnRequestDTO

config = ClientConfig(base_url="https://ciapi.cityindex.com/TradingAPI")

async with AsyncStoneXClient(config) as client:
    session = await client.session.log_on(
        ApiLogOnRequestDTO(
            UserName="username",
            Password="password",
            AppKey="app-key",
            AppVersion="stonepy",
            AppComments="",
        )
    )
    print(session.status_code)
```

Use `aclose()` for async clients when not using `async with`; use `close()` for sync clients.

## Error Handling

`StoneXError` is the base of the public runtime error hierarchy. Configuration validation and
plugin setup can also raise builtin `TypeError` or `ValueError` exceptions.

```python
from stonepy import (
    ClientConfig,
    RateLimitError,
    StoneXAPIError,
    StoneXClient,
    StoneXError,
)
from stonepy.models import ApiLogOnRequestDTO

config = ClientConfig(base_url="https://ciapi.cityindex.com/TradingAPI")

try:
    with StoneXClient(config) as client:
        client.session.log_on(
            ApiLogOnRequestDTO(
                UserName="username",
                Password="password",
                AppKey="app-key",
                AppVersion="stonepy",
                AppComments="",
            )
        )
except RateLimitError as exc:
    print(exc.retry_after)
except StoneXAPIError as exc:
    print(exc.http_status, exc.error_code, exc.error_message)
except StoneXError as exc:
    print(exc)
```

Important subclasses include `AuthenticationError`, `RateLimitError`,
`OrderRejectedError`, `OrderStatusUnknownError`, `ResponseParseError`, `StoneXAPIError`, and
`TransportError`.

## Pagination

Paginated API methods return the page DTO documented by StoneX. For example,
`client.market.list_market_search_paginated(...)` accepts `page`, `page_size`, and
`order_by` keyword arguments and returns `ListMarketSearchPaginatedResponseDTO`:

```python
page = client.market.list_market_search_paginated(
    "gold",
    search_by_market_code=False,
    search_by_market_name=True,
    spread_product_type=True,
    cfd_product_type=True,
    binary_product_type=False,
    ascending_order=True,
    include_options=False,
    client_account_id=12345,
    page=0,
    page_size=100,
)
print(page.total_number_of_results)
```

## API Reference

Full documentation - the guides and a complete API reference - is published at
<https://aaronmgn.github.io/stonepy/>.

## Development

```bash
uv venv
uv sync --extra dev
uv run pytest -q
uv run ruff check .
uv run ruff format --check .
uv run mypy
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for the full contributor guide and
[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), and [CHANGELOG.md](CHANGELOG.md) for release notes.

## Support

- Questions and bug reports: [GitHub issues](https://github.com/aaronmgn/stonepy/issues).
- Security: please report vulnerabilities privately - see [SECURITY.md](SECURITY.md).

## AI Use Disclaimer

Portions of this project, including the generated API bindings, DTO models, and documentation,
were produced with the assistance of AI tooling and reviewed by a human maintainer. The library
is tested against the StoneX CIAPI v2 contract but is provided "as is", without warranty of any
kind (see [LICENSE](LICENSE)).

`stonepy` is **unofficial** and is not affiliated with, endorsed by, or supported by StoneX,
City Index, or GAIN Capital. Trading carries financial risk; validate all behaviour against the
[official API documentation](https://docs.labs.gaincapital.com/) before using it with a live
account.
