Skip to main content

AI-agent-ready Python SDK for crypto perpetual futures order execution with TWAP and maker strategies.

Project description

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

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

from blockfill import Blockfill
print(Blockfill().version())   # → "blockfill 0.2.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.

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

bf.version() -> str
# Returns "blockfill 0.2.X"

Credentials

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

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:

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. "0.2.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):

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.
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

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

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:

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 for the authoritative list.

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

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

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

Patterns

Strategy system integration

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:

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

blockfill-0.2.0-py3-none-manylinux2014_x86_64.whl (7.7 MB view details)

Uploaded Python 3

File details

Details for the file blockfill-0.2.0-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for blockfill-0.2.0-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 446163b801324fba2459319d02664eb15aa8384b670cd7fa94861471368de2c1
MD5 1cf18df038d17f7b6f39ad0a21e80bb1
BLAKE2b-256 08ac1155613ad416c07ec93a7d823bba436e982e3f3b9e5020955894db8e0247

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page