Introduction
Most trading bot tutorials start with a single console application and slowly evolve into unmaintainable complexity:
- trading logic mixed with broker code
- logging scattered everywhere
- global runtime state
- no lifecycle tracking
- no operational telemetry
- no execution governance
At the other extreme, many architecture discussions immediately jump into:
- microservices
- CQRS
- event sourcing
- distributed actors
- Kubernetes
- enterprise-level abstraction layers
Neither extreme is ideal for an MVP trading platform.
This article walks through the architecture evolution of a lightweight but serious trading system built with:
- C#
- .NET
- Alpaca API
- Azure-ready deployment patterns
The goal was simple:
Build an architecture strong enough to evolve into a SaaS trading platform later, without overengineering the MVP.
The Core Philosophy
The architecture intentionally favors:
- practical layering
- operational clarity
- explainable execution
- incremental evolution
- execution-aware telemetry
- runtime correctness
- low ceremony
The system intentionally avoids:
- premature distributed systems
- unnecessary abstractions
- architecture for architecture’s sake
- enterprise-pattern overload
The focus is:
Build only what real runtime pressure requires.
Final Architecture
Tanolis.Trading.Console
Tanolis.Trading.Core.Domain
Tanolis.Trading.Core.Services
Tanolis.Trading.Infrastructure
Dependency Structure
Console
↓ references
Core.Services
↓ references
Core.Domain
Infrastructure
↓ references
Core.Services
↓ references
Core.Domain
Additionally:
Console
↓ references
Infrastructure
Visual Architecture Shape
Console

Layer Responsibilities
Tanolis.Trading.Console
Purpose:
- runtime host
- execution scheduler
- startup/bootstrap
- configuration loading
- dependency wiring
Examples:
- Program.cs
- execution timers
- appsettings loading
- runtime startup
The console project should NOT contain:
- trading logic
- broker implementation logic
- persistence logic
- lifecycle management
Tanolis.Trading.Core.Domain
Purpose:
- business meaning
- trading vocabulary
- lifecycle models
- domain constants
- runtime state models
Examples:
- TradeRecord
- TradeSignal
- BotState
- SymbolState
- ExitReasons
- TradeActions
- LogEvents
- LogLevel
- LogSources
The domain layer intentionally remains independent of:
- Alpaca SDK
- Azure SDKs
- SQL Server
- runtime hosting
- logging implementations
- persistence implementations
This keeps the business concepts clean and portable.
Tanolis.Trading.Core.Services
Purpose:
- execution orchestration
- trading workflows
- lifecycle coordination
- risk management
- application contracts
- runtime coordination
Examples:
- TradingService
- StateService
- TradeJournalService
- SmaStrategy
- RuntimeContext
- IBroker
- ILogService
Suggested internal structure:
Core.Services
Contracts
IBroker.cs
ILogService.cs
Configuration
TradingConfig.cs
AlpacaConfig.cs
Models
BrokerOrderResultDto.cs
OrderDto.cs
Trading
TradingService.cs
Strategies
SmaStrategy.cs
State
StateService.cs
Journaling
TradeJournalService.cs
Runtime
RuntimeContext.cs
Core.Services acts as the orchestration and application behavior layer.
Tanolis.Trading.Infrastructure
Purpose:
- external integrations
- broker connectivity
- logging implementations
- operational integrations
- persistence implementations
Examples:
- AlpacaBroker
- LogService
- Azure integrations
- future SQL implementations
- future Table Storage implementations
Infrastructure implements application contracts defined by Core.Services.
Example:
publicclassAlpacaBroker : IBroker
and:
publicclassLogService : ILogService
This creates clean dependency inversion while keeping the architecture lightweight.
Ports and Adapters Direction
One interesting architectural observation was that the platform naturally evolved toward:
Ports and Adapters
without intentionally overengineering for it.
Current mapping:
| Layer | Role |
|---|---|
| Core.Services | ports/contracts |
| Infrastructure | adapters |
| Core.Domain | business concepts |
| Console | composition root |
This created a clean separation between:
- business intent
- execution orchestration
- external implementations
- runtime hosting
without introducing unnecessary complexity.
Operational Telemetry Philosophy
One major architectural decision was treating logs as:
operational decision telemetry
instead of simple debug output.
This changed the entire design approach.
The system now tracks:
| Category | Purpose |
| Signal telemetry | why signals occurred |
| Execution telemetry | why trades executed or were blocked |
| Risk telemetry | governance decisions |
| Lifecycle telemetry | trade continuity |
| Runtime telemetry | operational health |
Examples:
TradeBlocked | DailyLossLimit
TradeBlocked | OpenOrderExists
TradeSkipped | SidewaysMarket
StateReconciled | Clearing stale state
This dramatically improves:
- debugging
- strategy analysis
- operational trust
- SaaS observability
- future analytics
Symbol-Scoped Runtime State
One of the most important architectural evolutions was moving from:
global runtime state
to:
symbol-scoped runtime state
Originally the bot stored:
one EntryPrice
one ActiveTradeId
one LastTradeTime
for ALL symbols.
This worked initially but became a serious correctness problem once the bot traded:
- AAPL
- MSFT
- NVDA
simultaneously.
The solution was introducing:
Dictionary<string, SymbolState>
inside:
BotState
Each symbol now maintains:
- isolated trade lifecycle
- isolated cooldowns
- isolated stop-loss state
- isolated reconciliation state
- isolated PnL tracking
This was one of the most important runtime architecture corrections in the platform.
Runtime Metadata and Trade Lifecycle Tracking
The platform also evolved into execution-aware lifecycle tracking.
The system now tracks:
| Identifier | Purpose |
| SessionId | bot runtime instance |
| CycleId | execution loop iteration |
| TradeId | trade lifecycle |
| OrderId | broker execution |
This enables:
- execution tracing
- operational diagnostics
- auditability
- lifecycle analytics
- future distributed execution support
without prematurely implementing distributed systems.
Why Minimal Architecture Matters
The biggest lesson from this architecture journey was:
Minimal architecture does NOT mean weak architecture.
The platform now supports:
- multi-symbol execution
- isolated runtime state
- execution governance
- structured telemetry
- lifecycle tracing
- broker abstraction
- operational reconciliation
- future cloud hosting
- SaaS evolution
while still remaining:
- understandable
- lightweight
- maintainable
- incremental
Future Direction
The current architecture is intentionally designed to evolve gradually toward:
- Azure Container Apps
- Azure Table Storage telemetry
- SQL Server + EF Core persistence
- Web API exposure
- Blazor dashboards
- analytics and reporting
- multi-user SaaS support
- distributed runtime workers
The key principle is:
Only evolve architecture when real operational pressure justifies it.
Final Thoughts
A successful MVP architecture is not the one with the most patterns.
It is the one that:
- survives growth
- remains understandable
- supports operational visibility
- evolves incrementally
- avoids unnecessary complexity
This trading platform architecture intentionally focused on:
practical engineering over architectural theater
And that balance turned out to be far more valuable than prematurely chasing enterprise complexity.

Add to favorites
