PersonalTrader — LangGraph multi-agent stock trading automation
Automated Korean and US stock trading where LLM agents in distinct roles split the work from research to order placement
Background
Automated stock trading bots usually follow a single strategy, or settle every judgment in one LLM call. PersonalTrader aimed for a different structure on live Korea Investment & Securities and Kiwoom Securities accounts. Separate LLM agents handle stock research, debate, risk assessment, and order execution. Every evening the day's trades are reviewed and fed back into the next morning's prompt.
Because the system connects to live accounts, the judgment layer (market data and analysis) had to be separated from the execution layer (orders) at the network level. Deployment also had to be staged.
System architecture
- Two runner layers:
simplified_runner.pyowns the four-stage main path of Scan→Score→Guard→Execute, and LangGraph sits above it as a Planning Graph (morning research, evening review) and a Session Graph (intraday monitoring). - Zero Trust three networks: Docker networks are split into
analysis_net(market data, LLM),execution_net(orders only), andshared_net(DB, Redis, Kafka), so an analysis container cannot reach the order API. - Multi-broker factory:
broker/factory.pyselects the KIS (domestic and overseas) or Kiwoom (domestic) adapter per account. Broker credentials are encrypted with Fernet before being stored in the DB. - Event bus: 15
EventTypevalues (Sense/Think/Act/Learn) flow across 7 Kafka topics, andAuditLogverifies sequence gaps through a hash chain.
How it was built
Simplifying the runner while keeping LangGraph. LangGraph nodes originally drove pipeline execution as well. That made debugging, retries, and the Celery integration all heavy. The main execution path was pulled out into a four-stage SimplifiedRunner (Scan→Score→Guard→Execute). LangGraph remained the LLM-driven decision layer in the Planning and Session Graphs. Celery tasks call SimplifiedRunner, and LangGraph nodes reuse the same runner where they need it.
Splitting the Planning Graph from the Session Graph. The LangGraph instances were split in two by time of day. The Planning Graph (planning_graph.py) handles pre-market research (yesterday's review plus today's candidate selection) and the post-close review. Celery Beat triggers it at fixed times. The Session Graph (session_graph.py) is a short graph that reacts minute by minute to monitor holdings and decide on new entries during the session. The split makes it immediately visible which graph's node stalled during debugging, and lets the non-realtime graph batch its LLM calls to cut cost.
Zero Trust network separation. The backend handles market data and analysis, and the executor places orders. They sit on analysis_net and execution_net respectively, with only the DB, Redis, and Kafka shared over shared_net. The analysis container cannot reach the order endpoint over TCP.
Fernet encryption of broker credentials. A master key from an environment variable is derived with SHA-256 into a Fernet key. The values are stored encrypted in the app_key_enc and secret_key_enc columns. A key_version column leaves room for key rotation.
Authentication and audit. /auth/login verifies with bcrypt first and issues a five-minute temporary token. Access and Refresh tokens are granted only after pyotp-based TOTP (time-based one-time password) verification passes. AuditLog chains hashes as prev_hash = SHA-256(prev_hash | ts | actor | action | resource | outcome) to detect tampering. In an emergency, POST /api/kill-switch sets a Redis flag that halts every pipeline immediately.
Four-stage staged rollout. The stages are BACKTEST → PAPER → CANARY → LIVE. BACKTEST replays historical OHLCV candles - open, high, low, close, volume - with slippage and fees applied, and PAPER uses the KIS simulated trading API. CANARY is a small live account with a budget cap, and LIVE requires the kill switch and a 3% daily loss limit. Deployment state is recorded in the DB so that each stage is a precondition for the next.
Learning loop. Every evening, per-trade attribution (attribution_signal/execution/regime), failure type, and lessons accumulate in ReflectionRecord. Those records are grouped into a DailyReflectionSummary. The next morning the Planning Graph injects that summary as feedback_context, so accumulated lessons reach the prompt.
Results
- Two-layer structure of SimplifiedRunner (four stages) plus LangGraph (Planning and Session Graphs) separates the execution path from the decision layer.
- Full stack designed and built solo: 75 backend API endpoints, 24 DB tables, 8 frontend pages, and 72 components.
- Sense/Think/Act/Learn events recorded across 7 Kafka topics and 15 EventTypes, with tamper verification through the hash-chained
AuditLog. - Security model for live accounts: Zero Trust three-network isolation, Fernet credential encryption, JWT plus TOTP 2FA, and a kill switch.
- KIS (domestic and overseas) and Kiwoom (domestic) unified behind a factory, so adding a broker means writing one adapter.
- Four deployment modes from BACKTEST through PAPER and CANARY to LIVE, plus a backtest framework built on risk-adjusted return metrics such as Sharpe, Sortino, MaxDD, and Calmar.