Note: This repository was generated by the 🍪 CookieBlueprint.
Night gathers, and now my watch begins. It shall not end until my portfolio dies. I shall take no emotional trades, hold no bags, chase no pumps. I shall wear no FOMO and win no glory. I shall live and die at my terminal. I am the bot in the darkness. I am the watcher on the charts. I am the shield that guards the realms of profit. I pledge my code and honor to the Night Watch, for this night and all the nights to come.
graph LR
%% Node Definitions
Fetch[/Market API/]
Fetch --> Kill{Kill Switch?}
Kill -- Yes --> Stop[🛑 Halt]
Kill -- No --> Strat{Strategy?}
Strat -- HOLD --> Fetch
Strat -- BUY / SELL --> Risk{Risk Check?}
Risk -- Blocked --> Log[📝 Log Reason]
Risk -- Approved --> Exec[🚀 Execute]
Exec --> Fetch
%% Styling
classDef red fill:#ffcdd2,stroke:#b71c1c,stroke-width:2px,color:#b71c1c;
classDef green fill:#c8e6c9,stroke:#1b5e20,stroke-width:2px,color:#1b5e20;
classDef blue fill:#bbdefb,stroke:#0d47a1,stroke-width:2px,color:#0d47a1;
classDef default fill:#f5f5f5,stroke:#333,stroke-width:1px;
class Kill,Stop,Log red;
class Exec green;
class Strat,Risk blue;
Fastest path to a running bot — everything containerized, nothing to install locally:
docker compose up --build
curl -s http://localhost:8000/healthz | jqThis starts Postgres, NATS, the bot itself (trade-service), and the full
observability stack. Grafana: localhost:3000 (admin/admin). Prometheus:
localhost:9090. See Running with Docker Compose below for the full service list
and health-check details.
For local development (editing code, running tests/linting without Docker), see
Setup below. For the bot's internal architecture and conventions, see AGENTS.md.
If not already installed, install WSL. Once thise is done make sure you run the following command
# Update the package index and install the build-essential packace (C/C++ compiler, Make, Librariey like libc6-dev, etc):
sudo apt update
sudo apt install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-devIf not already installed, install Python. The recommended way is to use pyenv, which allows multiple parallel Python installations which can be automatically selected per project you're working on.
# Install Python if necessary
pyenv install 3.11
pyenv shell 3.11If not already installed, get Poetry using pipx according to https://python-poetry.org/docs/#installation. If your are new to Poetry, you may find https://python-poetry.org/docs/basic-usage/ interesting.
Execute the following in a terminal:
# Create venv and install all dependencies
make
# Cleanup venv
make cleanDo not forget to activate your virtualenv when done with the makefile
The metrics below are a compatibility contract for dashboards and alerts.
Metric names and label keys are considered stable and MUST NOT change without
an explicit migration plan. All of them are exposed together on the single
trade-service process's /metrics endpoint — the "ingestion" / "trading" split
below is a logical grouping, not two separate running services (see AGENTS.md for
the full architecture).
ticks_received_total{symbol}(counter): total ticks received from the exchange per symbolticks_published_total{symbol}(counter): total ticks best-effort published to NATS core (subjectmarket.tick.<SYMBOL>) per symbol, for any interested external subscriber. A publish failure never blocks trading — seeAGENTS.mdfor the ingest pipeline.ws_reconnects_total(counter): total websocket reconnect attemptsparse_errors_total(counter): total ticker message parse failures
signals_total{symbol,side,strategy}(counter): total strategy-emitted signalssignals_rejected_total{symbol,reason}(counter): total signals rejected by risk checksorders_created_total{symbol,side}(counter): total orders created from approved signalsorders_filled_total{symbol,side}(counter): total paper-executed fillsposition_qty{symbol}(gauge): current open position quantity per symbolcash_balance(gauge): current quote-currency cash balanceequity(gauge): current total equity (cash + position value)fees_paid_total(counter): cumulative fees paid by the paper portfolio
kill_switch_trading_enabled(gauge): 1 when the kill switch currently allows trading, 0 when trading is killed. Reflects the current state — seekill_switch_toggles_total(counter) for how often it has changed.kill_switch_ready(gauge): 1 once the JetStream control backlog has been drained and kill-switch state restored; 0 means every tick is being suppressed regardless ofkill_switch_trading_enabled.kill_switch_available(gauge): 1 whenNATS_SERVERSwas configured at startup so the kill switch can receive commands at all; 0 means trading can never be halted remotely for this process's lifetime, only by stopping it. SetREQUIRE_KILL_SWITCH=trueto refuse to start in that state instead.
replay_ticks_total{symbol}(counter): total ticks republished to NATS by thereplayCLI, per symbolreplay_duration_seconds(histogram): time spent running a singlereplayCLI invocation, from file open to completion
At first glance one may be overwhelmed by the amount of files and folders present in this directory. This is mainly due to the fact, that each tool uses its own configuration file. The situation has improved with more and more tools adding support for pyproject.toml. The following two tables describe the main structure of the project:
| Folder | Purpose |
|---|---|
.venv |
This is where the Poetry-managed venv lives. |
.vscode |
This is where settings for vscode live. Some useful defaults are added in case you use vscode in your project. If not, this can savely be deleted. |
src |
Main directory for the python code. Most of the times this will contain one subfolder with the main module of the project. Nightwatch in our case. Replace this with your own module-name. |
tests |
Directory containing all tests. This directory will be scanned by the test-infrastructure to find testcases. |
| File | Purpose |
|---|---|
.gitattributes |
Attributes for git are defined in this file, such as automatic line-ending conversion. |
.gitignore |
This file contains a list of path patterns that you want to ignore for git (they will never appear in commits). |
.pre-commit-config.yaml |
This file contains configuration for the pre-commit hook, which is run whenever you git commit, you can configure running code quality tools and tests here. |
poetry.toml |
Configuration for Poetry. |
pyproject.toml |
This file contains meta information for your project, as well as a high-level specification of the dependencies of your project, from which Poetry will do its dependency resolution and generate the poetry.lock. Also, it contains some customization for code-quality tools. Check PEP 621 for details. |
README.md |
This file. Document how to develop and use your application in here. |
The following environment variables may be used to configure Nightwatch:
| Environment Variable | Purpose | Default Value | Allowed Values |
|---|---|---|---|
| DATABASE_URL | Postgres DSN. Required to run main.py. Accepts both the bare postgresql:// form and the SQLAlchemy-style postgresql+asyncpg:// form. |
unset (main.py raises RuntimeError) |
e.g. postgresql+asyncpg://trade:tradepass@trade-db:5432/trade |
| TRADE_SYMBOL | Symbol streamed from Kraken. | BTC/USD |
Any Kraken-supported pair |
| INITIAL_CASH | Starting cash; only used when no portfolio state is persisted yet. | 10000 |
Decimal |
| ORDER_NOTIONAL | Fixed quote-currency notional per BUY order. | 100 |
Decimal |
| FEE_RATE | Paper-execution fee rate, as a fraction of notional. | 0.001 |
Decimal |
| STRATEGY_WINDOW_SEC | Momentum strategy lookback window. | 10.0 |
Float, seconds |
| STRATEGY_THRESHOLD_PCT | Momentum threshold that triggers a signal. | 0.30 |
Float, percent |
| NATS_SERVERS | Comma-separated NATS URLs. Unset disables NATS and the kill switch entirely (trading is never gated) — see AGENTS.md. |
unset | Any reachable NATS URL list |
| NATS_TOKEN | NATS authentication token. | "" |
Free-form |
| HTTP_HOST / HTTP_PORT | uvicorn bind address / port. | 0.0.0.0 / 8000 |
host / port |
| HEALTH_REQUIRE_WS | Whether /healthz requires the Kraken WebSocket to be connected to report ok. docker-compose.yml sets this to "0". |
true |
1/true/yes/on or anything else |
| LOG_LEVEL | Sets the default log level here. | "INFO" |
See Python Standard Library API-Reference |
| LOG_FORMAT | json emits structured logs for Loki; anything else uses the human-readable UTC format. |
"text" |
text / json |
| MIGRATIONS_DIR | Overrides auto-discovery of the migrations/ directory. |
auto-discovered | Filesystem path |
| RUN_INTEGRATION | Gate integration tests (test_integration_*.py). |
unset | 1 to enable |
A docker-compose.yml is provided to run the trade-service together
with its dependencies (Postgres, NATS, Prometheus, and Grafana) locally or on a VPS:
| Service | Image | Purpose |
|---|---|---|
trade-db |
postgres:16-alpine |
Persistent Postgres instance (DB trade, user trade, password tradepass). |
nats |
nats:2.10-alpine |
NATS broker with JetStream enabled. |
trade-service |
built from local Dockerfile |
FastAPI app exposing /healthz and /metrics on port 8000. |
prometheus |
prom/prometheus:latest |
Scrapes trade-service metrics and exposes Prometheus UI on port 9090. |
grafana |
grafana/grafana:11.1.4 |
Dashboard UI on port 3000, auto-provisioned with a default Prometheus datasource. |
Spin everything up:
docker compose up --buildThen verify the service can reach Postgres and NATS:
curl -s http://localhost:8000/healthz | jq
# {
# "ok": true,
# "ws_connected": false,
# "nats_connected": true,
# "db_connected": true
# }The /healthz endpoint performs a live SELECT 1 against the database
on every request when a DatabaseConnector is injected, so
db_connected reflects the current reachability of trade-db. The
endpoint returns 200 when every connection is healthy and 503
otherwise.
Grafana is provisioned from grafana/provisioning/datasources/datasource.yml.
After startup, sign in at http://localhost:3000 (default admin / admin) and the
Prometheus datasource is already configured to http://prometheus:9090.
The default dashboard NightWatch Overview is auto-loaded from
grafana/provisioning/dashboards/nightwatch-overview.json.