Metadata-Version: 2.5
Name: zeroledger
Version: 0.1.0
Summary: ZeroLedger programmatic trading SDK — accounts, market & Swiss history data, order-book depth, order execution through the risk gate, server-side trailing-SL rules, and WebSocket streaming.
Project-URL: Homepage, https://zeroledger.org
Project-URL: Documentation, https://zeroledger.org/kb/api
Author: ZeroLedger
License: Proprietary
Keywords: api,forex,sdk,trading,zeroledger
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24
Provides-Extra: stream
Requires-Dist: websockets>=13; extra == 'stream'
Description-Content-Type: text/markdown

# zeroledger

The official Python SDK for the **ZeroLedger** programmatic trading API. Access your accounts, market and
historical data, order-book depth, place and manage orders **through the risk gate**, set server-side
trailing-stop rules, and stream live updates — all over a clean, broker-neutral interface.

## Install

```bash
pip install zeroledger            # REST
pip install "zeroledger[stream]"  # + WebSocket streaming
```

## Authenticate

Create an API key in the dashboard under **Configuration → API Keys**, grant it scopes, then:

```python
from zeroledger import ZeroLedger

zl = ZeroLedger(api_key="zl_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
print(zl.me())   # {key_id, scopes, account_scope, rate_limit_per_minute}
```

Everything you touch is **broker-blind**: symbols are standardized (`GOLD`, `EURUSD`) and every id is opaque
(`acct_…`, `pos_…`, `ord_…`, `rule_…`). No upstream broker is ever exposed.

## Accounts & positions  *(scopes: `accounts:read`, `trades:read`)*

```python
for a in zl.accounts():
    print(a["account_number"], a["type"], a["balance"], a["can_trade"])

acct = zl.accounts()[0]["id"]
zl.positions(acct)
zl.orders(acct)
zl.trades(acct, days=30)
```

## Market data  *(scope: `market:read`, depth: `depth:read`)*

```python
zl.symbols()
zl.symbol("GOLD")
zl.candles("GOLD", "1m", limit=100)          # recent OHLCV
zl.quotes(["GOLD", "EURUSD"])
zl.depth("GOLD")                             # live order-book snapshot
```

## Deep historical data (Swiss)  *(scope: `swiss:read`)*

```python
import time
end = int(time.time() * 1000)
start = end - 7 * 86400 * 1000
hist = zl.history("GOLD", "1h", start=start, end=end)      # columnar t/o/h/l/c/v + page_info
bars = zl.history("GOLD", "1h", start=start, end=end, as_object=True)["candles"]
zl.coverage("GOLD")                                        # earliest/latest per timeframe
```

Follow `hist["page_info"]["next_from"]` to page forward; a fully-closed range is `immutable`.

## Trading  *(scope: `trades:write` — every placement passes the risk gate)*

```python
order = zl.place_order(acct, symbol="GOLD", side="buy", type="market", volume=0.10,
                       sl=4100.0, tp=4200.0)       # Idempotency-Key auto-generated
limit = zl.place_order(acct, symbol="EURUSD", side="sell", type="limit",
                       price=1.1200, volume=0.50)

zl.modify_position(order["id"], sl=4120.0)          # safe partial — only the fields you send
zl.close_position(order["id"])                       # or volume=0.05 for partial
zl.cancel_order(limit["id"])
```

Placements are idempotent: retrying with the same `idempotency_key=` returns the original result instead of
executing twice.

## Server-side trailing stops  *(scopes: `rules:read`, `rules:write`)*

Rules run on ZeroLedger's servers and ratchet your stop even while your client is offline. Distances are in
price units.

```python
# trail a position's stop 5.0 behind price once it is 3.0 in profit
zl.trailing_stop(acct, order["id"], distance=5.0, activation=3.0)

# or any rule type: trailing_sl | move_to_breakeven | step_trail
zl.create_rule(acct, type="move_to_breakeven",
               target={"kind": "position", "position": order["id"]},
               params={"activation": 10.0, "offset": 0.5})

zl.rules(acct)
zl.delete_rule(acct, rule_id)
```

## Streaming  *(scope: `stream`, needs `zeroledger[stream]`)*

```python
for frame in zl.stream(["depth:GOLD", "quotes:EURUSD", "account:" + acct, "positions:" + acct]):
    if frame["type"] == "data":
        print(frame["channel"], frame["key"], frame["data"])
```

Channels: `quotes`, `depth` (market), `account`, `positions`, `orders` (your accounts). Frames arrive on
change.

## Errors

All failures raise a typed `ZeroLedgerError` subclass: `AuthError`, `ForbiddenError`, `NotFoundError`,
`ValidationError`, `OrderRejected` (carries `.code`/`.retcode`), `IdempotencyConflict`, `RateLimited`,
`UpstreamUnavailable`.

```python
from zeroledger import OrderRejected
try:
    zl.place_order(acct, symbol="GOLD", side="buy", type="market", volume=99.0)
except OrderRejected as e:
    print(e.code, e.message, e.retcode)
```
