Skip to content

About

And now my watch begins

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Note: This repository was generated by the 🍪 CookieBlueprint.

NightWatch

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.

Process (DAG)

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;
Loading

Getting Started

Fastest path to a running bot — everything containerized, nothing to install locally:

docker compose up --build
curl -s http://localhost:8000/healthz | jq

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

Setup

Install WSL

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

Install Python

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

Install Poetry

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

Create Environment

Execute the following in a terminal:

# Create venv and install all dependencies
make

# Cleanup venv
make clean

Do not forget to activate your virtualenv when done with the makefile

Usage

Metrics Contract

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

Ingestion

  • ticks_received_total{symbol} (counter): total ticks received from the exchange per symbol
  • ticks_published_total{symbol} (counter): total ticks best-effort published to NATS core (subject market.tick.<SYMBOL>) per symbol, for any interested external subscriber. A publish failure never blocks trading — see AGENTS.md for the ingest pipeline.
  • ws_reconnects_total (counter): total websocket reconnect attempts
  • parse_errors_total (counter): total ticker message parse failures

Trading

  • signals_total{symbol,side,strategy} (counter): total strategy-emitted signals
  • signals_rejected_total{symbol,reason} (counter): total signals rejected by risk checks
  • orders_created_total{symbol,side} (counter): total orders created from approved signals
  • orders_filled_total{symbol,side} (counter): total paper-executed fills
  • position_qty{symbol} (gauge): current open position quantity per symbol
  • cash_balance (gauge): current quote-currency cash balance
  • equity (gauge): current total equity (cash + position value)
  • fees_paid_total (counter): cumulative fees paid by the paper portfolio

Safety

  • kill_switch_trading_enabled (gauge): 1 when the kill switch currently allows trading, 0 when trading is killed. Reflects the current state — see kill_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 of kill_switch_trading_enabled.
  • kill_switch_available (gauge): 1 when NATS_SERVERS was 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. Set REQUIRE_KILL_SWITCH=true to refuse to start in that state instead.

Replay

  • replay_ticks_total{symbol} (counter): total ticks republished to NATS by the replay CLI, per symbol
  • replay_duration_seconds (histogram): time spent running a single replay CLI invocation, from file open to completion

Contents and Concepts

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.

Environment Variable

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

Running with Docker Compose

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

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

About

And now my watch begins

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages