Building a Minimal Yet Serious Trading Platform Architecture

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:

LayerRole
Core.Servicesports/contracts
Infrastructureadapters
Core.Domainbusiness concepts
Consolecomposition 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:

CategoryPurpose
Signal telemetrywhy signals occurred
Execution telemetrywhy trades executed or were blocked
Risk telemetrygovernance decisions
Lifecycle telemetrytrade continuity
Runtime telemetryoperational 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:

IdentifierPurpose
SessionIdbot runtime instance
CycleIdexecution loop iteration
TradeIdtrade lifecycle
OrderIdbroker 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.

FavoriteLoadingAdd to favorites

Author: Shahzad Khan

Software Developer / Architect

Leave a Reply