OverviewThis WebsiteHome LabTrading BotEcosimProcedural Generation
Source Code

src/pages/projects/trading_bot.rs

use leptos::*;
use leptos_meta::{Meta, Title};

use super::data::ALL_PROJECTS;

#[component]
pub fn TradingBot() -> impl IntoView {
    let project = ALL_PROJECTS
        .iter()
        .find(|p| p.slug == "trading-bot")
        .unwrap();
    let skills_view = project
        .skills
        .iter()
        .map(|&s| view! { <li>{s}</li> })
        .collect::<Vec<_>>();

    view! {
        <Title text="Trading Bot – Peter Pinto"/>
        <Meta name="description" content="A Kubernetes-native operator written in Rust that manages the full lifecycle of automated trading bot deployments via Custom Resource Definitions."/>
        <div class="page">
            <span class="eyebrow">"Projects"</span>
            <h1>"Trading " <em style="font-style:italic; color: var(--accent)">"Bot"</em></h1>
            <p class="lead">
                "A Kubernetes-native operator in Rust that manages the full lifecycle of automated
                trading bot deployments — configuration, scheduling, and risk management — through
                declarative Custom Resource Definitions."
            </p>

            <ul class="skills-list" style="margin-top: 1.5rem;">
                {skills_view}
            </ul>

            <hr class="divider"/>

            // ── Operator Architecture ─────────────────────────────
            <section class="project-section">
                <span class="eyebrow">"Architecture"</span>
                <h2>"Kubernetes Operator"</h2>
                <p>
                    "The core of the project is a Kubernetes operator built with "
                    <a href="https://kube.rs" target="_blank" rel="noopener noreferrer" class="prose-link">"kube-rs"</a>
                    ". The operator watches for "
                    <code class="inline-code">"TradingBot"</code>
                    " custom resources and reconciles the cluster state to match — creating
                    Deployments, ConfigMaps, and Services as needed, and cleaning them up
                    when the resource is deleted via Kubernetes owner references and finalizers."
                </p>
                <p style="margin-top: 1rem;">
                    "The binary runs in two modes selected at startup: "
                    <code class="inline-code">"--operator"</code>
                    " runs the controller loop in-cluster, while "
                    <code class="inline-code">"--api-server"</code>
                    " starts a standalone REST server exposing the Asset Registry. Both share
                    the same compiled binary, which eliminates separate deployment artifacts
                    and simplifies the release process."
                </p>
            </section>

            <hr class="divider"/>

            // ── CRDs ─────────────────────────────────────────────
            <section class="project-section">
                <span class="eyebrow">"API Design"</span>
                <h2>"Custom Resource Definitions"</h2>
                <p>
                    "Two CRDs form the public API of the operator. "
                    <code class="inline-code">"TradingBot"</code>
                    " describes a single bot instance: strategy type and image, target exchange
                    with API credentials sourced from a Kubernetes Secret, CPU/memory limits,
                    and a full risk management block. "
                    <code class="inline-code">"AssetRegistry"</code>
                    " manages a catalogue of infrastructure assets — API endpoints, database
                    connections, deployments, and config — with lifecycle tracking and
                    dependency edges between entries."
                </p>
                <p style="margin-top: 1rem;">
                    "Credentials are never stored in the operator or the database. The CRD
                    spec holds only a Secret name and key path; the reconciler performs a
                    targeted Secret lookup at runtime, keeping sensitive values out of any
                    persisted resource."
                </p>
                <div class="code-block" style="margin-top: 1.25rem;">
                    <pre><code>"apiVersion: trading.io/v1
kind: TradingBot
metadata:
  name: arb-bot-binance
spec:
  strategy:
    type: Arbitrage
    image: ghcr.io/example/arb-bot:latest
    parameters:
      minProfitThreshold: \"0.005\"
  exchange:
    name: Binance
    apiKeySecret: binance-creds
  riskManagement:
    maxPositionSize: \"1000\"
    dailyLossLimit: \"200\"
    stopLossPercentage: \"2.5\""</code></pre>
                </div>
            </section>

            <hr class="divider"/>

            // ── Asset Registry ────────────────────────────────────
            <section class="project-section">
                <span class="eyebrow">"Storage & API"</span>
                <h2>"Asset Registry"</h2>
                <p>
                    "The Asset Registry is a lightweight inventory system backed by "
                    <a href="https://www.sqlite.org" target="_blank" rel="noopener noreferrer" class="prose-link">"SQLite"</a>
                    " via "
                    <code class="inline-code">"sqlx"</code>
                    ". Assets have a type ("
                    <code class="inline-code">"ApiEndpoint"</code>
                    ", "
                    <code class="inline-code">"DatabaseConnection"</code>
                    ", "
                    <code class="inline-code">"KubernetesDeployment"</code>
                    ", and others), a status lifecycle (Draft → Active/Inactive/Failed), tags, and
                    UUID-based dependency references. The schema is created on first run — no
                    separate migration step."
                </p>
                <p style="margin-top: 1rem;">
                    "The REST API layer is built with Axum and documented automatically via "
                    <a href="https://github.com/juhaku/utoipa" target="_blank" rel="noopener noreferrer" class="prose-link">"utoipa"</a>
                    ", which generates an OpenAPI 3.0 spec directly from Rust type annotations.
                    Full CRUD is exposed at "
                    <code class="inline-code">"/api/v1/assets"</code>
                    " with filtering by type, status, and tag."
                </p>
            </section>

            <hr class="divider"/>

            // ── Risk Management ───────────────────────────────────
            <section class="project-section">
                <span class="eyebrow">"Safety"</span>
                <h2>"Risk Management"</h2>
                <p>
                    "Each "
                    <code class="inline-code">"TradingBot"</code>
                    " resource carries a declarative risk management block, making limits an
                    explicit part of the resource definition rather than a runtime concern:"
                </p>
                <ol class="project-steps">
                    <li><strong>"Max position size"</strong>" — caps the notional value of any single open position"</li>
                    <li><strong>"Daily loss limit"</strong>" — halts the bot for the calendar day once reached"</li>
                    <li><strong>"Stop-loss / take-profit"</strong>" — per-trade percentage thresholds"</li>
                    <li><strong>"Max open positions"</strong>" — limits simultaneous exposure"</li>
                </ol>
                <p style="margin-top: 1rem;">
                    "Because these parameters live in the CRD spec, they are version-controlled,
                    auditable, and can be updated with a plain "
                    <code class="inline-code">"kubectl apply"</code>
                    " — the operator picks up the change on the next reconcile cycle."
                </p>
            </section>

            <hr class="divider"/>

            // ── Infrastructure ────────────────────────────────────
            <section class="project-section">
                <span class="eyebrow">"Infrastructure"</span>
                <h2>"Build & Deploy"</h2>
                <p>
                    "The operator ships as a multi-stage Docker image. The builder stage uses the
                    official Rust image to compile a statically-linked binary; the final stage is
                    a minimal distroless image running as a non-root user, keeping the attack
                    surface small. RBAC manifests grant the operator only the specific Kubernetes
                    permissions it actually needs — no cluster-admin shortcuts."
                </p>
                <p style="margin-top: 1rem;">
                    "A Docker Compose file spins up a local "
                    <a href="https://kind.sigs.k8s.io" target="_blank" rel="noopener noreferrer" class="prose-link">"Kind"</a>
                    " cluster alongside mock Binance and Coinbase exchange APIs and a Swagger UI
                    instance, providing a self-contained development environment with no external
                    dependencies."
                </p>
            </section>

            <hr class="divider"/>

            // ── Status ────────────────────────────────────────────
            <section class="project-section">
                <span class="eyebrow">"Status"</span>
                <h2>"Core Architecture Complete"</h2>
                <p>
                    "The operator, REST API, and CRD definitions are fully implemented and compile
                    cleanly. Remaining work is additive: trading strategy containers (the operator
                    already manages their lifecycle), and fleshing out status-reporting — condition
                    arrays and deployed-resource tracking are scaffolded and ready to be populated
                    by the reconciler."
                </p>
            </section>
        </div>
    }
}