Docs · Python SDK · v0.1.0

The wd Python SDK

A small, pure-stdlib SDK that ships with every WatchDog Bot. Provides structured logging, secure credential access, and runtime environment introspection. No pip install required — it's always available when your bot runs.

Overview

The wd SDK is a thin layer over the stdlib that gives you three things:

All functions are pure-stdlib and have no external dependencies. The SDK is auto-injected into your bot's sys.path at runtime, so import wd always works.

Installation

No installation required. The SDK is bundled with WatchDog Bot and auto-injected into sys.path when your bot script runs. Just:

import wd

If you want to import wd outside the platform (e.g. for local development), the source lives in your install at resources/sdk/wd/. You can copy that folder next to your script.

wd.log

Structured logging that writes to stdout in a format the dashboard understands. Output appears in real time on the bot's logs tab.

Methods

wd.log.info(message: str, *args)
wd.log.warning(message: str, *args)
wd.log.error(message: str, *args)
wd.log.debug(message: str, *args)

Each method accepts a format string and arguments, like logging or printf:

import wd

wd.log.info("Bot started")
wd.log.info("Connected to %s as %s", exchange, account_id)
wd.log.warning("rate limit at %.0f%% — slowing down", pct)
wd.log.error("API call failed: %s", err)

Output format

Each line follows the form [LEVEL] message — the platform parses this for the level filter in the UI:

[INFO]  Bot started
[INFO]  Connected to kalshi as user_xyz
[WARNING] rate limit at 87% — slowing down
[ERROR] API call failed: ConnectionTimeout

Tip: wd.log.debug() messages are tagged but treated as INFO in the dashboard so they don't appear in red. Use them for verbose tracing you want to keep in the codebase.

wd.connection

Look up an API connection by name. Returns a Connection object with api_key, api_secret, and base_url attributes — pulled from the encrypted connection store you configured in Settings.

wd.connection(name: str) -> Connection

Example

conn = wd.connection("Kalshi")
print(conn.api_key)        # "ABC123..."
print(conn.api_secret)     # "***PEM***"
print(conn.base_url)       # "https://api.elections.kalshi.com/trade-api/v2"

Slug tolerance

The lookup is case-insensitive and ignores most punctuation. All of these resolve to the same connection:

wd.connection("Kalshi")
wd.connection("kalshi")
wd.connection("KALSHI")
wd.connection("My Kalshi")  # if you named it that

Resolution order

For a connection named Kalshi, wd.connection looks for these environment variables in order, returning the first one that resolves to a non-empty value:

AttributeEnv vars tried (in order)
api_keyKALSHI_KEY → KALSHI → KALSHI_API_KEY
api_secretKALSHI_SECRET → KALSHI_PRIVATE_KEY → KALSHI_API_SECRET
base_urlKALSHI_URL → KALSHI_BASE_URL → KALSHI_ENDPOINT

Security

The Connection object's repr() masks secrets, so accidentally logging it won't leak credentials:

print(repr(conn))
# Connection(name='Kalshi', api_key='ABC***...', api_secret='***', base_url='https://...')

Errors

If the connection name is empty or doesn't exist, raises ValueError with a descriptive message. The bot should let this propagate — debugging a missing connection at runtime is easier than handling an unset attribute later.

wd.bot_id

Returns the unique UUID assigned to this bot at creation. Stable across restarts. Useful for distinguishing instances when you run multiple copies of the same bot code.

wd.bot_id() -> str
wd.log.info("Bot %s starting", wd.bot_id())
# [INFO] Bot 7f3a-c8b1-a4e6 starting

wd.bot_name

Returns the human-readable name you gave the bot.

wd.bot_name() -> str
print(wd.bot_name())
# "Kalshi NYC Weather"

wd.bot_dir

Returns a pathlib.Path for the bot's working directory. Use this for any files the bot writes (state, caches, etc.) — they persist across restarts and are scoped to this bot only.

wd.bot_dir() -> pathlib.Path
import json
state_file = wd.bot_dir() / "state.json"

if state_file.exists():
    state = json.loads(state_file.read_text())
else:
    state = {"trades": 0, "pnl": 0.0}

# ...run logic, modify state...

state_file.write_text(json.dumps(state))

wd.is_demo

Returns True if the bot is running in demo mode (the platform's "dry run" environment — no real orders placed). Use it to gate destructive actions during testing.

wd.is_demo() -> bool
if wd.is_demo():
    wd.log.info("DEMO mode — orders will not be placed")
else:
    place_real_order(...)

End-to-end example

A complete, runnable bot using every part of the SDK:

import time, json
import wd

# Pull encrypted credentials by connection name
conn = wd.connection("Kalshi")

# Persistent state in the bot's working directory
state_file = wd.bot_dir() / "state.json"
state = json.loads(state_file.read_text()) if state_file.exists() else {"orders": 0}

wd.log.info("Bot %s (%s) starting — demo=%s", wd.bot_name(), wd.bot_id(), wd.is_demo())

while True:
    try:
        # ...your trading logic using conn.api_key, conn.api_secret...

        if not wd.is_demo():
            # place real order
            state["orders"] += 1
            wd.log.info("Order placed — total this run: %d", state["orders"])
        else:
            wd.log.debug("would-place: skipping in demo")

        state_file.write_text(json.dumps(state))
        time.sleep(30)

    except Exception as e:
        wd.log.error("tick failed: %s", e)
        time.sleep(60)