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:
- Structured logging via
wd.log— appears in the dashboard with level filters and timestamps - Secure credential access via
wd.connection(name)— pulls API keys from the encrypted store, never from source - Environment introspection via
wd.bot_id(),wd.is_demo(), etc. — useful for distinguishing demo vs live runs
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
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.
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:
| Attribute | Env vars tried (in order) |
|---|---|
| api_key | KALSHI_KEY → KALSHI → KALSHI_API_KEY |
| api_secret | KALSHI_SECRET → KALSHI_PRIVATE_KEY → KALSHI_API_SECRET |
| base_url | KALSHI_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.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.
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.
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.
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)
WatchDog Bot