Skip to main content

Python SDK for the blockfill execution daemon

Project description

blockfill Python SDK

Python wrapper for the blockfill execution daemon. Manage exchange credentials, run the daemon, and operate tickets (place / query / cancel) over a local UDS RPC socket.

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

As a library (from blockfill import Blockfill)

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

As a CLI only (blockfill ticket place ...)

pipx install blockfill

(pipx keeps the SDK in its own isolated venv and links the blockfill / bf CLI to ~/.local/bin/. On Debian 12+ / Ubuntu 23+, pipx is the recommended way to install Python CLI apps without hitting PEP 668's "externally-managed-environment" error.)

Upgrading

pip will not auto-upgrade an already-installed package — re-running pip install blockfill is a no-op if any version is already there. To pull the latest release:

pip install -U blockfill           # latest
pip install blockfill==0.1.4       # pin specific version
pipx upgrade blockfill             # if installed via pipx

Check the live version on PyPI:

pip index versions blockfill
# or
curl -s https://pypi.org/pypi/blockfill/json | python3 -c \
    "import sys,json;print(json.load(sys.stdin)['info']['version'])"

What you get

The wheel ships with the executor binary pre-built. No GitHub release, no install() call — pip install is everything. The qtex endpoint and API key are also hardcoded into the binary; users never set them.

Verify the install

One-liner sanity check before anything else:

from blockfill import Blockfill
print(Blockfill().version())   # → "blockfill 0.1.4"

If that prints a version, everything is wired up correctly. If it errors:

Error Meaning
ModuleNotFoundError: No module named 'blockfill' wrong Python env (forgot source .venv/bin/activate?)
BinaryNotFound: ... _bin/blockfill wheel missing the bundled binary (re-install with --force-reinstall)
OSError: [Errno 8] Exec format error wrong-platform wheel installed (shouldn't happen on a fresh install)
GLIBC_X.X not found host glibc older than manylinux2014 (= glibc 2.17)

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(data_dir="~/.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.health()  # 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")

# Query persistent history (qtex MongoDB; survives daemon restarts)
historical = bf.query(status="COMPLETE", history=True)

# Cancel
bf.cancel(ticket.ticket_id)

# Shut down daemon
bf.stop()

API Reference

Blockfill(data_dir, binary_path, timeout_s)

Parameter Default Description
data_dir ~/.blockfill Data directory (config, socket, logs, trading log)
binary_path bundled _bin/blockfill Override the bundled binary path (rarely needed)
timeout_s 10.0 Default RPC call timeout (seconds)

Version

bf.version() -> str
# Returns "blockfill 0.1.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)

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.health() -> 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).
exchanges list[str] Configured exchanges (from config.toml).
ready_exchanges list[str] Exchanges whose executor finished warmup.
active_tickets int NEW/OPEN tickets currently tracked.
uptime_s int Daemon process uptime in seconds.
version str Daemon binary version (e.g. "0.1.14").
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.
exchange (prop) dict[str, ExchangeStatus] Per-exchange status — see below.

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 (kline fetch, market state, api module — ready_cb fired). ready=False covers three cases: daemon not running, warmup in progress, or init failing in retry loop.


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,  # 10_000 .. 86_400_000
) -> 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

bf.cancel_all() -> int

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() -> int
# Call a SIGNED endpoint on each configured exchange to verify api_key+secret
# really authenticate. Exit code 0 = all pass. Detects:
# - wrong key/secret
# - IP whitelist mismatch (your outbound IP not on Binance whitelist)
# - testnet/mainnet flag wrong
# - network / proxy / geo block (signed REST round-trip implies reachability)
# Auto-invoked at the end of `set_credentials(...)` / `blockfill
# set-credentials` so typos surface immediately.

Positions

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

Proxy / Geo-bypass (for Starchild AI, US-IP users, etc.)

bf.set_proxy("http://jp:x@sc-vpn.internal:8080")  # Starchild sc-vpn skill
# Or any HTTP CONNECT proxy URL:
bf.set_proxy("http://user:pass@proxy.example.com:8080")
bf.set_proxy(None)                                  # remove

bf.start()  # daemon picks up the proxy from config.toml

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 paying for IPRoyal: after bf.set_proxy(...) set credentials — the SDK's auto check_credentials will fail loudly if the proxy can't reach the exchange, so you know before you start the daemon.


Context Manager

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

Exceptions

Exception When
BinaryNotFound bundled binary missing — pip install --force-reinstall blockfill
DaemonNotRunning daemon socket not found or unreachable
DaemonStartTimeout start() timed out waiting for the daemon
RpcError(code, message) daemon returned a JSON-RPC error
TicketNotFound cancel(ticket_id=...) called with non-existent ticket
CredentialsError invalid exchange name in set_credentials()
InvalidApiKey qtex rejected the embedded BLOCKFILL_API_KEY (HTTP 401)

Patterns

Strategy system integration

from blockfill import Blockfill

bf = Blockfill()

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

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

Check open positions

open_tickets = bf.query(status="NEW") + bf.query(status="OPEN")
for t in open_tickets:
    print(t.symbol, t.target_position, t.init_position, t.executed_position)

Audit past runs

# In-memory query only sees the current session.
# Use history=True to reach qtex MongoDB for COMPLETE / CANCEL from past sessions.
recent_completes = bf.query(status="COMPLETE", history=True, from_ms=ts_ms_24h_ago)

Directory Structure

~/.blockfill/
├── config.toml              # exchange credentials (chmod 0600)
├── runtime/
│   ├── daemon.sock          # UDS socket (SDK ↔ daemon IPC)
│   ├── daemon.pid           # PID file
│   └── daemon.log           # daemon logs
└── trading-log/             # Channel B upload retry buffer

The daemon binary lives inside the installed package (site-packages/blockfill/_bin/blockfill), not in ~/.blockfill/.


Supported Exchanges

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

⚠️ Symbol format differs per exchange

Each exchange uses its own native symbol format. blockfill does NOT cross-translate — pass the format the target exchange expects:

Exchange Format Example
binance-futures Lowercase, concatenated btcusdt, dogeusdt
okx-swap Dash-separated, includes -SWAP suffix BTC-USDT-SWAP, DOGE-USDT-SWAP
# Binance — lowercase native
bf.place(exchange="binance-futures", symbol="dogeusdt", target_position=100)

# OKX — dash + SWAP suffix native
bf.place(exchange="okx-swap", symbol="DOGE-USDT-SWAP", target_position=-100)

Passing the wrong format returns cancel_reason="rejected" immediately (no order ever reaches the exchange).

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.1.23-py3-none-manylinux2014_x86_64.whl (7.7 MB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for blockfill-0.1.23-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 617a2b6f2e51db1d5d56dca82bf633a14281ca9905584958318c7713ff16e48c
MD5 8e0359c25e87de9f6ebad60d0d9295a0
BLAKE2b-256 961999dde649c5555c8601dfb2bf8f27217fec6cff2478dc443fa8fa53017f82

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