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