PaperTrader
A distributed microservices platform simulating real-time stock trading using event-driven WebSockets and gRPC.
Project Background
PaperTrader was developed as the capstone project for the Higher Diploma in Software Development. The objective was to design and deliver a software solution that addressed a real-world problem while applying concepts such as distributed systems, APIs, database design and service communication. I chose a paper-trading platform because it combined real-time data processing, transactional workflows and user-facing application design within a single project.
User Interface & Experience
PaperTrader was designed to provide a simpler and more approachable trading experience than a traditional trading terminal. The interface surfaces the most relevant information while continuing to deliver real-time updates and portfolio visibility.
Key Features
01 / Real-time Market Data
PaperTrader consumes live market data streams and updates user-facing market information in real time, allowing users to react to changing conditions without refreshing the browser.
02 / Simulated Trade Execution
Trades are processed using live market prices while maintaining complete separation from real financial systems. This allows users to experiment with strategies without financial risk.
03 / Portfolio Tracking
Users can monitor holdings, account balances and transaction history through a continuously updated portfolio dashboard.
04 / Transaction History
Every trade is persisted and recorded, providing users with a complete audit trail of portfolio activity and performance.
05 / Multi-Service Architecture
Market data ingestion, trade execution and user-facing application responsibilities are separated across dedicated services, improving scalability and maintainability.
System Architecture
01 / Ingestion
Node.js Market Service streams real-time data from Finnhub WebSockets and continuously updates a Redis price cache.
02 / Gateway
FastAPI Web Server manages JWT user sessions, serves the UI, and routes trade orders via synchronous gRPC.
03 / Execution
Python Trading Service reads the Redis cache to execute isolated transaction rules, persisting history to PostgreSQL.
Deployment & Infrastructure
PaperTrader is containerized using multi-stage Dockerfiles for each respective microservice, orchestrated natively via Docker Compose to handle internal container network bridging. This allows the services to securely leverage internal container aliases for gRPC operations without exposing ports to the public internet.
The ecosystem is deployed onto an AWS EC2 production instance. Incoming web traffic is routed through Cloudflare WAF for DNS management and handled on the server via a Caddy reverse proxy, which manages automated HTTPS TLS certificate provisioning and secure upstream request routing.
Engineering Decisions & Tradeoffs
01 / Trades cannot manipulate their own execution prices
When an order is submitted, the frontend only sends the
information required to execute the trade. The execution
price is retrieved directly from Redis by the Trading
Service at the moment of validation. This prevents users
from tampering with price data in the browser.
Tradeoff:
Additional service complexity in exchange for stronger
execution integrity.
02 / Market data and user data are deliberately stored separately
Real-time price updates arrive continuously and generate a
large volume of writes. To prevent these updates from
impacting transactional performance, transient market data
is stored in Redis while PostgreSQL is reserved for user
accounts and completed trades.
Tradeoff:
More infrastructure components in exchange for better
scalability and database protection.
Future Development
01 / Live market updates can occasionally lag during periods of heavy activity
During periods of unusually high market activity, the market data ingestion service can occasionally struggle to keep pace with incoming updates. While the application automatically reconnects and recovers gracefully, users may briefly see delayed dashboard updates when processing large bursts of live market data. Potential Improvement: Replace the current ingestion worker with a more highly concurrent solution capable of processing larger volumes of incoming events without impacting the user experience.
02 / Trade execution is tightly coupled to the transaction service
Trade requests are currently processed immediately through direct service-to-service communication. This approach keeps the system simple and performs well for a modest user base, but it provides limited resilience if transaction volumes increase significantly or if downstream services become temporarily unavailable. Potential Improvement: Introduce a message-driven architecture using a queue or event broker to decouple trade submission from trade execution and improve system resilience under load.