Metadata-Version: 2.4
Name: blockfill
Version: 0.1.44
Summary: AI-agent-ready Python SDK for crypto perpetual futures order execution with TWAP and maker strategies.
Project-URL: Homepage, https://gitlab.com/qts1100445/blockfill-agent-execution
Project-URL: Source, https://gitlab.com/qts1100445/blockfill-agent-execution
Project-URL: Documentation, https://gitlab.com/qts1100445/blockfill-agent-execution/-/blob/release/1.0.1/README.md
Project-URL: Issues, https://gitlab.com/qts1100445/blockfill-agent-execution/-/issues
Project-URL: PyPI, https://pypi.org/project/blockfill/
Author-email: Quantech <jordan@quantech.services>
License: Proprietary
Keywords: ai-agent,algorithmic-trading,binance-futures,blockfill,crypto-trading,execution-algorithm,maker-execution,mcp,okx,order-execution,perpetual-futures,trading-sdk,twap
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
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: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: toml
Description-Content-Type: text/markdown

# BlockFill Agent Execution

BlockFill Agent Execution is an AI-agent-ready Python SDK and local execution
engine for crypto perpetual futures order execution. It enables AI agents,
trading systems, and developers to execute large crypto perpetual futures
orders through TWAP and maker-style strategies on Binance Futures and OKX
Swap, while keeping exchange API keys on the user's own machine.

You declare a target position; BlockFill works the order over a time window
using **maker** (PostOnly resting + IOC fallback) or **twap** (taker-sliced)
strategies, cutting execution slippage vs. naïve market orders.

One Python API, one local daemon, two exchanges supported today
(`binance-futures`, `okx-swap`), one `bf.place(...)` call per target position.

## Designed for AI Agents

BlockFill Agent Execution is designed to be called by AI agents, trading
copilots, MCP servers, strategy systems, and execution workflows.

Typical agent instruction:

> Buy 100,000 USDT worth of BTC perpetuals over 30 minutes using TWAP, with
> local API-key signing and no third-party custody.

BlockFill provides the execution layer behind that instruction:

1. The agent decides the trading intent.
2. BlockFill converts the intent into an executable ticket.
3. The local daemon signs and places orders through the user's own exchange
   API keys.
4. The agent can query ticket status, positions, and execution results.

## Security Model

BlockFill Agent Execution is **self-custodial by design**.

Exchange API keys stay on the user's own machine (`~/.blockfill/config.toml`,
chmod 0600). The local daemon signs orders locally and sends them directly to
the exchange. BlockFill does not custody user funds, store exchange API keys,
or route orders through a third-party trading server — no key custody, no
order routing, no order-flow visibility by a third party. The package only
ships the execution engine.

## Requirements

- Python 3.10+
- **linux-x86_64** — the daemon binary is bundled inside the wheel. PyPI publishes a `manylinux2014_x86_64`-tagged wheel only; macOS / Windows / arm64 hosts will be cleanly rejected by `pip` with "no matching distribution".

## Install

```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -U blockfill
```

`-U` is intentional: it installs the latest release if you don't have it, and
upgrades in place if you do (without it, `pip install blockfill` is a no-op
when any version is already installed).

## Verify the install

```python
from blockfill import Blockfill
print(Blockfill().version())   # → "blockfill 1.0.X"
```

## Quickstart

> ⚠️ **Order matters.** `bf.start()` will refuse to launch if no exchange
> credentials are configured. Always call `set_credentials()` first on a
> fresh machine — see `DaemonStartTimeout: no config.toml at ...` below.

```python
from blockfill import Blockfill

bf = Blockfill()

# 1) Write exchange credentials to ~/.blockfill/config.toml (chmod 0600).
#    SDK auto-runs `check_credentials` (a signed REST round-trip) — proves
#    auth works AND the host can reach the exchange. If you're behind a
#    geo block (US → binance), set a proxy first via bf.set_proxy(...).
bf.set_credentials("binance-futures", api_key="...", api_secret="...", testnet=True)

# 2) Start daemon. ~50s warmup while it fetches market data.
bf.start()
bf.status()  # returns DaemonStatus(running=False, ...) if anything is wrong

# Place a ticket
ticket = bf.place(
    exchange="binance-futures",
    symbol="btcusdt",
    strategy="maker",
    target_position=0.1,
    time_constraint_ms=300_000,
)
print(ticket.ticket_id, ticket.status)  # tkt_xxx NEW

# Query active session (in-memory)
tickets = bf.query(status="NEW")

# Cancel
bf.cancel(ticket.ticket_id)

# Shut down daemon
bf.stop()
```

---

## API Reference

### Version

```python
bf.version() -> str
# Returns "blockfill 1.0.X"
```

---

### Credentials

```python
bf.set_credentials(
    exchange: str,            # "binance-futures" | "okx-swap"
    api_key: str,
    api_secret: str,
    api_passphrase: str | None = None,  # OKX only
    testnet: bool = False,
) -> None
# Writes the [exchanges.<name>] block of {data_dir}/config.toml (chmod 0600),
# runs check_credentials() to print verification, then stops + starts the
# daemon (when running) so the new user_id/creds load immediately — avoids
# identity confusion where `place` / history queries would otherwise keep
# operating under the OLD creds cached in the running daemon.
```

The qtex endpoint and API key are **compiled into the binary** at release time — you do not configure them.

---

### Daemon

```python
bf.start(wait_timeout_s=10.0, env=None) -> None
# Spawns the daemon in the background, returns once the UDS socket is bound.
# Idempotent — no-op if already running.

bf.stop(wait_timeout_s=5.0) -> None
# Graceful shutdown.

bf.restart() -> None

bf.status() -> DaemonStatus
# Always returns a DaemonStatus (`running=False, ...` when the daemon is
# not reachable — never raises). Single entry point for everything you
# need to know about the daemon's state.
```

For debugging, tail the daemon log directly:

```bash
tail -F ~/.blockfill/runtime/daemon.startup.log
```

`DaemonStatus` fields:

| Field             | Type                        | Meaning                                                                                                                                                                                                                                                               |
| ----------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `running`         | `bool`                      | Daemon process is up and the RPC socket answered.                                                                                                                                                                                                                     |
| `pid`             | `int`                       | Daemon process id (0 if not running).                                                                                                                                                                                                                                 |
| `exchange` (prop) | `dict[str, ExchangeStatus]` | Per-exchange status keyed by name — see below. **Primary way** to check what's configured and what's ready.                                                                                                                                                           |
| `active_tickets`  | `int`                       | NEW/OPEN tickets currently tracked.                                                                                                                                                                                                                                   |
| `uptime_s`        | `int`                       | Daemon process uptime in seconds.                                                                                                                                                                                                                                     |
| `version`         | `str`                       | Daemon binary version (e.g. `"1.0.X"`).                                                                                                                                                                                                                               |
| `proxy`           | `str \| None`               | Active outbound proxy URL (or None for direct).                                                                                                                                                                                                                       |
| `qtex`            | `bool`                      | True if qtex is reachable. qtex hosts the ticket-history endpoints (`/public/v1/tickets/*`) used by `bf.query(history=True)`; when down, live trading is unaffected but history queries fail. Updated by a 10s background ping; flips False after 30s of no response. |

`exchanges` (`list[str]`, configured names) and `ready_exchanges`
(`list[str]`, warmed-up names) are also present on the dataclass as
flat-list shortcuts — they're just `list(exchange.keys())` and
`[n for n,e in exchange.items() if e.ready]` respectively. Prefer
`exchange[name].ready` in code.

Per-exchange readiness (the only way to check ready — there is no
top-level `ready` flag because "all-ready" is rarely meaningful in a
multi-exchange daemon):

```python
s = bf.status()
s.exchange
# → {
#     "binance-futures": ExchangeStatus(ready=True),
#     "okx-swap":        ExchangeStatus(ready=False),
#   }

if s.exchange["binance-futures"].ready:
    bf.place(exchange="binance-futures", ...)
```

`ready=True` means the executor finished warmup (REST symbol/book fetch,
authenticated `get_account_balances`, market-stream connect — `ready_cb`
fired). `ready=False` covers:

- daemon not running
- warmup in progress (typical 30–60s after `bf.start()`)
- init failed and supervisor is in its retry backoff (bad creds, IP
  whitelist, geo block, exchange API down)

`bf.place(exchange=X, ...)` while `X` is not ready is rejected at the RPC
layer with `-32000 X not ready — executor still warming up; poll
system.status until ready_exchanges contains it` — no ticket is created.

---

### Tickets

#### Strategies

| `strategy` | Behavior                                                                                                                                                                                                                                                          |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"maker"`  | **Passive maker.** Posts PostOnly limit orders that sit on the book. In the last segment of the time window, falls back to IOC to clean up any unfilled remainder. Lower fees (maker rebate when available), no guarantee of full fill if the book never crosses. |
| `"twap"`   | **Pure-taker TWAP.** Places IOC orders on a TWAP schedule across the time window — no PostOnly phase. Guarantees completion at the cost of crossing the spread on every slice.                                                                                    |

```python
bf.place(
    exchange: str,
    symbol: str,
    strategy: str = "maker",        # "maker" | "twap" (see table above)
    target_position: float,          # positive = long, negative = short
    time_constraint_ms: int = 300_000,  # 60_000 .. 86_400_000 (1min .. 24h)
) -> Ticket

bf.query(
    status: str | None = None,       # "NEW" | "OPEN" | "COMPLETE" | "CANCEL"
    symbol: str | None = None,
    ticket_id: str | None = None,
    from_ms: int | None = None,
    to_ms: int | None = None,
    limit: int = 100,
    history: bool = False,           # False=in-memory; True=qtex MongoDB
) -> list[Ticket]

bf.cancel(ticket_id=None, symbol=None, all=False) -> None | int
# - cancel(ticket_id="tkt_...") -> None     # raises TicketNotFound if missing
# - cancel(symbol="btcusdt")    -> int      # cancel NEW+OPEN for that symbol
# - cancel(all=True)            -> int      # cancel everything active
```

`Ticket` fields:

| Field                 | Type            | Notes                                                   |
| --------------------- | --------------- | ------------------------------------------------------- |
| `ticket_id`           | `str`           | `tkt_<hex>`                                             |
| `status`              | `str`           | `NEW` / `OPEN` / `COMPLETE` / `CANCEL`                  |
| `exchange`            | `str`           | `binance-futures` / `okx-swap`                          |
| `symbol`              | `str`           | exchange-format symbol                                  |
| `strategy`            | `str`           | `maker` / `twap`                                        |
| `target_position`     | `float`         | requested net position                                  |
| `init_position`       | `float \| None` | exchange position at activation time                    |
| `executed_position`   | `float \| None` | actual delta filled so far                              |
| `time_constraint_ms`  | `int`           | execution time limit                                    |
| `start_time_ms`       | `int \| None`   | set when executor activates the ticket (NEW → OPEN)     |
| `last_update_time_ms` | `int \| None`   | refreshed on every state change                         |
| `is_expired`          | `bool`          | flag-only; status stays OPEN until separately cancelled |
| `cancel_reason`       | `str \| None`   | see table below                                         |

**`cancel_reason` values**: `external`, `superseded`, `stale`, `rejected`, `min_notional`, `risk_breach`, `insufficient_margin`, `paused`

**Auto-supersede**: placing a new ticket for the same `exchange+symbol` immediately cancels any existing `NEW`/`OPEN` ticket for that pair (`cancel_reason="superseded"`). The superseded ticket remains in query results.

---

### Diagnostics

```python
bf.check_credentials() -> None
# Calls a SIGNED REST endpoint on each configured exchange and prints one
# line per exchange:
#   ✓ binance-futures       3 asset balances returned
#   ✗ okx-swap              API error 50101: APIKey does not match current environment.
# Detects: wrong key/secret, IP whitelist mismatch, testnet/mainnet flag
# wrong, network / proxy / geo block. Does not raise, does not return a
# status code — visual output is the signal.
# Auto-invoked at the end of `set_credentials(...)`.
```

### Positions

```python
bf.positions() -> list[dict]
# Each entry: {exchange, symbol, size, entry_price, update_ts_ms}
# Aggregated across all running executors.
```

### Proxy / Geo-bypass

For hosts that can't reach Binance directly (US IPs return HTTP 451),
route exchange REST traffic through an HTTP CONNECT proxy.

**Starchild users** — the free **`sc-vpn`** skill provides a managed
proxy across 18 countries (500 GB/month, no credentials). Pick a country
code and pass the URL:

```python
bf.set_proxy("http://jp:x@sc-vpn.internal:8080")   # Japan
bf.set_proxy("http://sg:x@sc-vpn.internal:8080")   # Singapore
bf.set_proxy("http://hk:x@sc-vpn.internal:8080")   # Hong Kong
bf.set_proxy()                                     # clear
```

Country codes (ISO-2):

| Asia-Pacific     | Europe              | Americas    |
| ---------------- | ------------------- | ----------- |
| `jp` Japan       | `uk` United Kingdom | `ca` Canada |
| `sg` Singapore   | `de` Germany        | `br` Brazil |
| `hk` Hong Kong   | `fr` France         | `mx` Mexico |
| `kr` South Korea | `nl` Netherlands    |             |
| `tw` Taiwan      | `ch` Switzerland    |             |
| `au` Australia   | `it` Italy          |             |
| `in` India       | `es` Spain          |             |
|                  | `se` Sweden         |             |

For binance, `jp` / `sg` / `hk` give the lowest latency. See the
[sc-vpn skill repo](https://github.com/Starchild-ai-agent/official-skills/tree/main/sc-vpn)
for the authoritative list.

You can also pass any HTTP CONNECT proxy URL (residential / paid):

```python
bf.set_proxy("http://user:pass@proxy.example.com:8080")
```

`set_proxy` auto-restarts the daemon (when running) so the new proxy
takes effect immediately — the daemon reads the proxy only at startup
and stashes it in a global, so a write-without-restart would leave the
running daemon on the OLD proxy.

The proxy applies to **all REST traffic** from daemon → exchange.
WebSocket proxy is on the TODO list — until then, daemon's market-data
streams go direct (and will fail on geo-blocked hosts).

Verify before committing: after `bf.set_proxy(...)` re-set credentials —
`set_credentials` auto-runs `check_credentials` which does a signed REST
round-trip through the proxy. A failure there means the proxy can't reach
the exchange, so you find out before starting the daemon.

---

### Context Manager

```python
with Blockfill() as bf:
    bf.start()
    ticket = bf.place(...)
# daemon is stopped on exit
```

---

## Patterns

### Strategy system integration

```python
from blockfill import Blockfill

bf = Blockfill()

if not bf.status().running:
    bf.start()

# On each signal
ticket = bf.place(
    exchange="binance-futures",
    symbol=symbol,
    strategy="maker",
    target_position=position,
    time_constraint_ms=300_000,
)
```

---

## Supported Exchanges

| Exchange        | Value               |
| --------------- | ------------------- |
| Binance Futures | `"binance-futures"` |
| OKX Swap        | `"okx-swap"`        |

### ⚠️ Symbol format differs per exchange

Each exchange has its own native symbol format. BlockFill does NOT
cross-translate — `bf.place(symbol=...)` must match exactly what the
exchange uses:

| Exchange          | Format                         | Example          |
| ----------------- | ------------------------------ | ---------------- |
| `binance-futures` | lowercase, concatenated        | `dogeusdt`       |
| `okx-swap`        | dash-separated, `-SWAP` suffix | `DOGE-USDT-SWAP` |

Don't guess — look it up with `bf.instruments(<substring>)`. It scans
all configured exchanges and returns native-format matches:

```python
bf.instruments("doge")
# [
#   {"exchange": "binance-futures", "symbol": "dogeusdt",        ...},
#   {"exchange": "okx-swap",        "symbol": "DOGE-USDT-SWAP",  ...},
#   ...
# ]

bf.place(exchange="binance-futures", symbol="dogeusdt",       target_position=100)
bf.place(exchange="okx-swap",        symbol="DOGE-USDT-SWAP", target_position=-100)
```

Wrong format → ticket auto-cancelled with `cancel_reason="rejected"`; no
order ever reaches the exchange.

---

## FAQ

### What is BlockFill Agent Execution?

BlockFill Agent Execution is an AI-agent-ready Python SDK and local execution
engine for crypto perpetual futures order execution.

### Can AI agents use BlockFill?

Yes. AI agents, MCP servers, trading copilots, and strategy systems can call
the BlockFill Python SDK to place and manage execution tickets.

### Does BlockFill custody user funds or API keys?

No. Exchange API keys stay on the user's machine. The local daemon signs
orders locally and sends them directly to the exchange.

### Which exchanges does BlockFill support?

BlockFill currently supports Binance Futures and OKX Swap.

### Which execution strategies are supported?

BlockFill currently supports TWAP execution and maker-style execution.

### Is BlockFill an exchange?

No. BlockFill is not an exchange. It is an execution SDK and local order
execution engine that connects to supported exchanges through user-owned API
keys.

---

## Related Concepts

BlockFill Agent Execution is related to AI agent trading, crypto order
execution, TWAP execution, maker execution, algorithmic trading, perpetual
futures trading, Binance Futures execution, OKX Swap execution, MCP trading
tools, and self-custodial trading infrastructure.
