Skip to content

Repository files navigation

Kairos

Kairos

Kairos is an AI-assisted crypto trading system: a CrewAI pipeline that researches coins, generates a rule-based strategy, and backtests it honestly, paired with a headless, restart-safe trading engine that runs that strategy live — one shared decision function driving both the backtest and the live loop, so they can never quietly disagree with each other.

Kairos = the opportune moment, as against chronos, sequential time. In its original archery and weaving senses it meant the aperture — the opening an arrow must pass through, the gap in the warp a shuttle must cross — which exists for an instant and then closes. That's a trade entry, described literally: act on the bar that just closed, not a wall-clock poll.

Why a rewrite

This replaced an earlier bot that was left running unattended and audited on return. The audit found roughly thirty defects in the live path, several of them compounding: mainnet market data feeding a strategy that executed on testnet; a quantity-rounding bug that could permanently deadlock closing a position; no live stop-loss at all — risk parameters were computed and displayed, never enforced; backtest metrics that were retyped by the LLM rather than written by the tool that computed them. None of it was patched. The engine, the backtester, and the strategy-eval path were all rebuilt around a small number of fixes that structurally can't regress — a shared decide() function, a Decimal-exact ledger, an AST-sandboxed rule evaluator instead of eval(), real reconciliation on startup.


Key Features & Capabilities

  • AI-Assisted Strategy Generation

    • Three sequential CrewAI agents — trending_coin_findercoin_pickerbacktester — research coins, pick one, and iterate a rule-based strategy until it clears a bar.
    • Runs on Vertex AI (Gemini) by default, with Google AI Studio as a fallback via one env var — model names live unprefixed in agents.yaml and resolve to whichever provider is active.
  • Backtesting That Doesn't Lie

    • Intrabar stop-loss/take-profit fills at the trigger price using the bar's real high/low, not the close — a take-profit that gaps past its target books the target, not the gap.
    • Real round-trip fees and slippage, charged in currency terms, not tacked on as an afterthought.
    • Sharpe ratio annualizes off the strategy's actual timeframe, not a fixed trading-days constant.
    • The tool that runs the backtest writes its own result file, atomically, only on a Keep recommendation — the LLM reports a summary string, it never gets a chance to retype a number.
  • A Rule Sandbox, Not eval()

    • Entry/exit rules are LLM-authored pandas boolean expressions, validated by an AST whitelist before they ever touch a DataFrame — no __import__, no attribute-chain sandbox escapes, no silent and/or scalar bugs.
  • A Headless Engine Built on One Shared Decision

    • signals.decide() is the single function that decides enter/exit/hold — driven bar-by-bar in the backtester, and driven live off every closed candle. Backtest and live can't diverge on what counts as a signal, because they're calling the same code.
    • Two independent cadences: a bar tick evaluates the compiled strategy on every closed candle; a risk tick polls price every few seconds so a stop-loss fires within seconds of being breached, not at the next bar close.
  • Real Persistence, Real Recovery

    • Every fill, position, and log line lives in SQLite with money stored as exact Decimal text — never a float.
    • On startup, the ledger is reconciled against the exchange's real balances and trade history before anything trades; it never invents an entry price it can't account for.
    • A heartbeat-based single-instance guard refuses to start a second copy against the same account — the old dashboard's biggest risk was two browser tabs quietly placing duplicate orders; this engine has no client-side ticking at all.
  • Pluggable Venues

    • paper (simulated fills over real market data), testnet, and mainnet — switchable by one config value, default testnet. Mainnet requires an explicit KAIROS_I_UNDERSTAND_REAL_MONEY=1 on top of selecting it.
  • A Live Dashboard, No Build Step

    • FastAPI + Server-Sent Events, a single static page (Tailwind via CDN, vanilla JS), hand-rolled inline-SVG candlestick chart with entry/exit markers and stop/take lines — no charting library, no bundler.
    • Optional bearer-token auth (KAIROS_AUTH_TOKEN) gates the API for a public deployment; unset, everything stays frictionless for local use.
  • Ships as a Container

    • A multi-stage Dockerfile + Compose file (Kairos behind Caddy for TLS) ready for a droplet — built for an unattended, multi-week run, not a demo.

Architecture

Strategy generation

sequenceDiagram
    participant User
    participant Crew as CrewAI crew
    participant Finder as trending_coin_finder
    participant Picker as coin_picker
    participant Backtester as backtester
    participant Tool as BacktestTool
    participant File as backtest_results.json

    User->>Crew: uv run run_crew
    Crew->>Finder: research trending coins
    Finder-->>Crew: candidate list
    Crew->>Picker: pick the best candidate
    Picker-->>Crew: chosen coin
    Crew->>Backtester: generate and validate a strategy
    loop until recommendation is Keep
        Backtester->>Tool: entry/exit rules, risk params
        Tool->>Tool: compile_rule (AST sandbox), then decide() bar by bar
        Tool-->>Backtester: win rate, Sharpe, drawdown, trade count, recommendation
    end
    Tool->>File: atomic write, Keep only, generated_by stamped
    Tool-->>Backtester: summary string only, never the raw numbers
Loading

Live trading engine

graph TD
    Feed["feed.py<br/>CandleFeed - closed candles only"]
    BarTick["bar tick<br/>every closed candle"]
    RiskTick["risk tick<br/>every KAIROS_RISK_POLL_SECONDS"]
    Decide["signals.decide()"]
    Broker["broker.py<br/>Broker Protocol"]
    PaperB["paper_broker.py"]
    BinanceB["binance_broker.py<br/>testnet / mainnet"]
    Store[("store.py<br/>SQLite ledger")]
    API["api/server.py<br/>FastAPI + SSE"]
    UI["api/static<br/>Dashboard"]

    Feed --> BarTick
    BarTick --> Decide
    Decide -->|enter / exit| Broker
    RiskTick -->|ticker price vs stop/take| Broker
    Broker --> PaperB
    Broker --> BinanceB
    BinanceB -->|real market data + fills| Feed
    BarTick <--> Store
    RiskTick <--> Store
    BarTick -->|state / bar / fill / log events| API
    RiskTick -->|state / bar / fill / log events| API
    API -->|SSE + REST| UI
Loading

Project Structure

kairos/
├── src/kairos/
│   ├── main.py                  # crew entry point
│   ├── crew.py                  # KairosCrew - agent/task wiring
│   ├── llm.py                   # Vertex AI / AI Studio provider resolver
│   ├── indicators.py            # one shared indicator definition (backtest + live)
│   ├── rules.py                 # AST-whitelist rule compiler/evaluator
│   ├── signals.py               # decide() - the shared bar-decision function
│   ├── timeframes.py            # timeframe <-> minutes
│   ├── config/                  # agents.yaml, tasks.yaml
│   ├── tools/                   # fetch_tool.py, backtest_tool.py
│   ├── engine/                  # the headless trading engine
│   │   ├── config.py            #   Settings from KAIROS_* env vars
│   │   ├── broker.py            #   Broker Protocol + make_broker() factory
│   │   ├── binance_broker.py    #   testnet/mainnet REST wiring
│   │   ├── paper_broker.py      #   simulated fills over real market data
│   │   ├── feed.py              #   closed-candle-only pipeline
│   │   ├── store.py             #   SQLite ledger, Decimal-exact
│   │   └── service.py           #   the asyncio loop + reconciliation
│   └── api/
│       ├── server.py            # FastAPI + SSE, KAIROS_AUTH_TOKEN gate
│       └── static/              # index.html, app.js, theme.css - no build step
├── tests/                       # ~175 tests, all offline (FakeBroker, no network)
├── Dockerfile
├── docker-compose.yml           # kairos + caddy
├── Caddyfile
├── output/                      # generated strategies (gitignored)
└── data/                        # SQLite ledger (gitignored)

Installation & Setup

Prerequisites

Dependency Purpose
Python 3.10–3.13 Core runtime
uv Package management
A Binance account Mainnet API key (market data, and paper venue) + Testnet API key (default trading venue)
A GCP project with Vertex AI enabled, or a Google AI Studio key Powers the CrewAI agents
ntfy (optional) Push notifications on fills

1. Clone & install

git clone https://github.com/pushkqr/kairos
cd kairos
uv sync

2. Configure environment

Copy .env.example to .env and fill in your values — every variable the app reads is documented there. A few worth calling out:

KAIROS_LLM_PROVIDER=vertex            # vertex (default) | aistudio
VERTEX_PROJECT=your-gcp-project-id
VERTEX_LOCATION=us-central1

KAIROS_VENUE=testnet                  # paper | testnet (default) | mainnet
KAIROS_I_UNDERSTAND_REAL_MONEY=       # must be exactly "1" to allow mainnet

Vertex AI auth: locally, run gcloud auth application-default login once. On a headless deployment (Docker/droplet), that interactive flow isn't available — use a service-account key instead (GOOGLE_APPLICATION_CREDENTIALS pointed at the JSON; docker-compose.yml already wires this up, you just supply gcp-key.json).

Some AI Studio API keys 404 on older model names (e.g. gemini-2.5-flash"no longer available to new users") even though they're still listed by the models endpoint. gemini-3.5-flash is confirmed working on both providers — swap to it in agents.yaml if you hit this on the aistudio fallback.


Usage

Generate a strategy

uv run run_crew

The crew researches, picks a coin, and iterates a strategy against real historical data until the backtester recommends Keep — at which point output/backtest_results.json is written atomically. A Modify/Discard recommendation writes nothing there; every attempt is still logged under output/backtest_runs/ for reference.

Run the engine + dashboard

uv run serve

Open http://localhost:8000. This starts the trading loop (against whatever KAIROS_VENUE and KAIROS_STRATEGY_FILE point at) and the live dashboard in one process — Ctrl+C stops both.

Run it in Docker (the intended deployment shape)

docker compose up -d

Builds the image, starts Kairos bound to 127.0.0.1:8000 (debugging access only), and starts Caddy in front of it on 80/443. Before this is actually reachable publicly:

  1. Point a real domain's DNS A record at the host, and replace your-domain.com in Caddyfile with it.
  2. Set KAIROS_AUTH_TOKEN in .env — a random string (e.g. python -c "import secrets; print(secrets.token_urlsafe(16))") — before anything is public.
  3. Make sure gcp-key.json is present next to docker-compose.yml if you're on Vertex AI.

./data and ./output are bind-mounted, so the ledger and any generated strategy survive a container restart or redeploy.


Testing

uv run pytest      # ~175 tests, fully offline - no network calls anywhere
uv run ruff check src/ tests/

The keystone fixture is FakeBroker (tests/conftest.py), a full in-memory implementation of the Broker Protocol — it's what makes the feed, the ledger, reconciliation, and the entire trading loop testable without ever touching a live exchange.


Safety Notes

  • Default venue is testnet. Promotion to mainnet requires changing KAIROS_VENUE and setting KAIROS_I_UNDERSTAND_REAL_MONEY=1 — a config change, deliberately not something a strategy or a bug can trigger on its own.
  • paper trades simulated fills over real mainnet market data. It validates the engine's plumbing (orders round-trip, stop-loss fires, restart reconciles) — it is not evidence about real fill quality, since there's no real order book behind it.
  • Kairos will place real orders on whatever venue is configured, including testnet. Nothing here is financial advice, and a strategy's backtest performance is not a promise about how it trades live.

Disclaimer

Kairos executes trades autonomously once running. Review any strategy before letting it trade, understand the venue you've pointed it at, and never enable mainnet without being genuinely comfortable with what that means for real funds.

About

Kairos — the opportune moment. AI-generated crypto strategies, backtested honestly, run by a headless engine that never stops watching the stop-loss.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages