Metadata-Version: 2.4
Name: tickerall
Version: 0.2.0
Summary: Official Python client for the TickerAll REST + WebSocket API — place trades, stream live market data, and manage broker sessions without an MT4/MT5 terminal in the path.
Project-URL: Homepage, https://tickerall.com
Project-URL: Documentation, https://tickerall.com/docs
Project-URL: Repository, https://github.com/TickerAll/tickerall
Author: Miguel Santos
License: MIT
License-File: LICENSE
Keywords: algo,api,broker,forex,mt4,mt5,sdk,tickerall,trading,websocket
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.25
Requires-Dist: websocket-client>=1.6
Provides-Extra: dev
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest>=7.4; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# tickerall

Official Python client for the [TickerAll](https://tickerall.com) REST + WebSocket API.

Place trades, stream live market data, and manage broker sessions programmatically — **without an MT4/MT5 terminal in the path**. No Windows VM, no Wine, no `MetaTrader5` terminal to babysit, no thread-safety workarounds.

```bash
pip install tickerall
```

Requires Python 3.9+. Depends only on [`httpx`](https://www.python-httpx.org/) and [`websocket-client`](https://github.com/websocket-client/websocket-client).

## Why

The official `MetaTrader5` Python package only runs on Windows, drives a local terminal over a single-threaded IPC channel, and falls over under concurrency. TickerAll hosts the broker connection for you and exposes it as a clean HTTP + WebSocket API, so your bot can run anywhere — Linux, macOS, a container, a Raspberry Pi — and stream ticks instead of polling.

| | `MetaTrader5` (local terminal) | `tickerall` |
|---|---|---|
| OS | Windows only | anywhere Python runs |
| Live ticks | poll `symbol_info_tick()` per symbol | push over WebSocket |
| Concurrency | single-threaded IPC, not thread-safe | stateless HTTP, thread-safe |
| Deploy | a terminal per account to babysit | `pip install` |

## Quickstart

```python
from tickerall import Tickerall

client = Tickerall(api_key="cf_live_...")

# Connect a broker account → get a TickerAll account_id
session = client.sessions.start(
    broker="mt5",
    server="Exness-MT5Trial7",
    account=12345678,
    password="...",
)

# Place a market order
order = client.orders.place(
    session.account_id,
    type="market",
    symbol="BTCUSDm",
    side="BUY",
    volume=0.10,
    stop_loss=58000.0,
    take_profit=72000.0,
)
print(order.ticket, order.status)

client.sessions.end(session.account_id)
```

The client is a context manager too:

```python
with Tickerall(api_key="cf_live_...") as client:
    ...
```

## Mesh mode (advanced)

Mesh mode routes your broker traffic through a pool topology: you connect your
accounts once against a **mother** endpoint, which warms them on a pool and hands
back that pool's **edge** URL plus a short-lived **grant**. The SDK then sends all
broker-data / trade / stream traffic to the edge (grant-authed) while roster and
discovery stay on the mother — refreshing the grant, and following a pool
migration, transparently. Opt in with `mesh_mode=True`; leaving it off is the
normal direct connect, unchanged.

```python
client = Tickerall(
    api_key="cf_live_...",           # authenticates you to the mother at discover time
    mesh_mode=True,
    mother_url="https://mesh.tickerall.com",
)

# Connect one or more accounts (they land on ONE pool together). Returns a
# stable account_ref per account — use it for every call and stream channel.
result = client.mesh.connect([
    {"broker": "mt5", "server": "Pepperstone-Demo", "account": 61542511, "password": "..."},
])
ref = result.accounts[0].account_ref

# From here the API is identical to direct mode — it just runs against the edge.
symbols = client.accounts.symbols(ref)
info    = client.accounts.account_info(ref)     # live balance/equity/…
stream  = client.stream.connect()
stream.subscribe_ticks(ref, ["BTCUSDm"])
```

The grant (1-hour TTL by default) is refreshed for you — proactively before it
expires, and reactively on a 401 or a stream drop — so you never handle it. The
`account_ref` is stable across refreshes **and** pool migrations, so subscriptions
and queries keyed on it never need remapping. `client.mesh.close()` stops the
background refresh.

## Terminal type (MOBILE / WEB / CLIENT)

`terminal_type` picks which client the connection presents AS — `"MOBILE"` (the
default), `"WEB"`, or `"CLIENT"` (a desktop terminal). All expose the full surface
(account, quotes, positions, history). The type sets the broker-assigned order
origin (`ENUM_DEAL_REASON`): `"MOBILE"` → `DEAL_REASON_MOBILE`, `"WEB"` →
`DEAL_REASON_WEB`, `"CLIENT"` → `DEAL_REASON_CLIENT` — useful where a venue
distinguishes desktop-placed orders (e.g. some prop firms).

`"WEB"` **requires the broker's web-terminal URL** (`web_terminal_url`) — web
terminals are per-broker-domain, so the URL must be supplied. `"MOBILE"` and
`"CLIENT"` take neither web field:

```python
session = client.sessions.start(
    broker="mt5",
    server="YourBroker-Server",
    account=12345678,
    password="...",
    terminal_type="WEB",
    web_terminal_url="https://mt5.yourbroker.com",  # required for WEB
    # web_endpoint="wss://host/path",               # optional WS override (rare)
)
```

For a desktop-origin (`DEAL_REASON_CLIENT`) connection — no web URL needed:

```python
session = client.sessions.start(
    broker="mt5",
    server="YourBroker-Server",
    account=12345678,
    password="...",
    terminal_type="CLIENT",
)
```

## Streaming — push, not poll

The stream runs on its own background thread. Register callbacks and go; it
heartbeats, reconnects with backoff, and re-subscribes automatically.

```python
client = Tickerall(api_key="cf_live_...")
session = client.sessions.start(broker="mt5", server="Exness-MT5Trial7",
                                account=12345678, password="...")

stream = client.stream.connect()
stream.on("tick", lambda e: print(e.symbol, e.bid, e.ask, e.timestamp))
stream.on("position", lambda e: print(e.event, e.position.ticket, e.position.profit))
stream.subscribe_ticks(session.account_id, ["BTCUSDm", "ETHUSDm"])
stream.subscribe_positions(session.account_id)

# ... your app runs ...
stream.close()
```

### Keep an in-memory tick cache fresh (zero polling)

A common pattern: let the WebSocket fill a dict so price reads are O(1) with no
network call — strictly better than polling a terminal per symbol.

```python
latest: dict[str, "TickEvent"] = {}
stream = client.stream.connect()
stream.on("tick", lambda e: latest.__setitem__(e.symbol, e))
stream.subscribe_ticks(session.account_id, ["BTCUSDm", "ETHUSDm", "XAUUSDm"])

# Anywhere in your app — instant, no IPC, no thread-safety dance:
tick = latest.get("BTCUSDm")
```

## Market data & history

```python
# Historical OHLC candles (coarser timeframes reach further back)
bars = client.candles.get(session.account_id, symbol="BTCUSDm", hours=24, timeframe="M5")
for c in bars:
    print(c.timestamp, c.open, c.high, c.low, c.close)

# Closed-trade history (recent broker window)
trades = client.history.get(session.account_id, symbol="BTCUSDm", limit=100)

# Tradeable symbols and their volume specs
symbols = client.accounts.symbols(session.account_id)
specs = client.accounts.symbol_specs(session.account_id)  # min / max / step per symbol

# Remove an account from your roster (disconnects it + drops it from your list
# and billing; broker account and open positions are untouched). Reversible —
# reconnect the same login with sessions.start to re-add it.
client.accounts.remove(session.account_id)
```

## Positions

```python
detail = client.accounts.get(session.account_id)
for p in detail.positions:
    print(p.ticket, p.symbol, p.side, p.volume, p.profit)

client.positions.modify(session.account_id, ticket=p.ticket, stop_loss=60000.0)
client.positions.close(session.account_id, ticket=p.ticket)          # full close
client.positions.close(session.account_id, ticket=p.ticket, volume=0.05)  # partial
```

## Always-hot sessions & transparent re-arm

For connections that must stay up across restarts, use `keep_alive`. The
credentials live in this process's memory only (never persisted); if the
account goes cold (e.g. TickerAll restarted), the next call transparently
re-supplies them and retries once.

```python
session = client.sessions.keep_alive(broker="mt5", server="Exness-MT5Trial7",
                                     account=12345678, password="...")
# ... later, after an outage, this just works — the client re-arms under the hood:
client.accounts.get(session.account_id)

# Stop keeping it alive (drops the cached credentials):
client.sessions.stop_keep_alive(session.account_id)
```

## Reliability — idempotency & queue-and-replay

State-changing calls (`sessions.start`, `orders.place`, `positions.close`,
`positions.modify`) carry a stable **Idempotency-Key**, so a retried call can't
double-execute. By default a transient connectivity failure
(`TickerallServiceUnavailableError`, `.transient == True`) **fails fast** so you
can re-decide with fresh prices:

```python
from tickerall import TickerallServiceUnavailableError

try:
    client.orders.place(account_id, type="market", symbol="BTCUSDm", side="BUY", volume=0.1)
except TickerallServiceUnavailableError:
    ...  # momentary blip — safe to retry
```

For price-insensitive orders (pending orders, SL/TP edits) you can instead
**queue-and-replay** until connectivity returns:

```python
client.orders.place(
    account_id, type="limit", symbol="BTCUSDm", side="BUY", volume=0.1, price=60000.0,
    queue_if_reconnecting=True, queue_max_s=60.0,
)
```

## Errors

All errors derive from `TickerallApiError` and carry `.status`, `.code`,
`.request_id`, `.details`, and `.transient`:

| Class | When |
|---|---|
| `TickerallAuthError` | 401 — bad/missing API key |
| `TickerallForbiddenError` | 403 — plan limit / reserved resource |
| `TickerallValidationError` | 400 / 422 — malformed request |
| `TickerallNotFoundError` | 404 — account / position not found |
| `TickerallBrokerError` | broker rejected or could not satisfy the request |
| `TickerallServiceUnavailableError` | transient — TickerAll momentarily unreachable (safe to retry) |

## Using it from an async app

REST methods are synchronous and thread-safe, so call them from an event loop
via `asyncio.to_thread`:

```python
detail = await asyncio.to_thread(client.accounts.get, account_id)
```

The stream is already non-blocking (its own thread) — callbacks fire as events
arrive.

## License

MIT © Miguel Santos
