Developer GuideArchitecture

Architecture

Copinance OS follows clean (hexagonal) architecture with strict boundaries: domain contracts, data and infra adapters, core orchestration and pipelines, AI for language and tool use only, and interfaces (CLI) at the edge.

Design principles

  1. Financial and quantitative work is deterministic — Implemented in domain models, data providers, analytics (QuantLib), and core.execution_engine executors. Outputs are structured and testable.
  2. LLMs do not replace computation — They explain, route questions to tools, and format answers. Tool implementations perform fetches and numeric work.
  3. Contracts over raw blobs — Prefer Pydantic models and explicit result types between layers; avoid passing untyped pandas frames across module boundaries.
  4. One front door for job executionResearchOrchestrator wraps the JobRunner port so use cases, CLI, and integrations do not scatter direct calls to executors. Override JobRunner only when you need custom orchestration (retries, queues, multi-tenant routing).

Layer map

┌──────────────────────────────────────────────────────────────────────────┐
│  interfaces/cli          `main` → dispatch: Typer vs generic question;   │
│                          Rich output or `--json` on analyze/market       │
└───────────────────────────────┬──────────────────────────────────────────┘

┌───────────────────────────────▼─────────────────────────────────────────┐
│  research/workflows     Use cases (analyze, market, profile, backtest)  │
│                           Build requests / jobs; call orchestrator      │
└───────────────────────────────┬─────────────────────────────────────────┘

┌───────────────────────────────▼─────────────────────────────────────────────────────────────────────────────┐
│  core/orchestrator        ResearchOrchestrator → JobRunner                                                  │
│  core/execution_engine    AnalysisExecutor implementations                                                  │
│  core/pipeline/tools      ToolRegistry, discovery (PluginSpec / entry points / scan), data & regime tools   │
└───────────────────────────────┬─────────────────────────────────────────────────────────────────────────────┘

┌───────────────────────────────▼───────────────────────────────┐
│  domain                   Models, ports, strategy protocols   │
│  (no external I/O)                                            │
└───────────────────────────────┬───────────────────────────────┘

┌───────────────────────────────▼─────────────────────────────────┐
│  data                     Providers, cache, repositories,       │
│                           loaders, analytics (options Greeks)   │
│  ai/llm                   Providers, prompts (explanation)      │
│  infra                    Settings, logging, DI container       │
└─────────────────────────────────────────────────────────────────┘

There are no domain/portfolio or domain/risk packages — implement those against domain.ports and wire them in infra/di like any other adapter.

Package tree (source)

Full src/copinance_os/ layout. For a product-oriented overview, see the README.

copinance_os/
├── ai/
│   └── llm/                            # Providers (Gemini, OpenAI, Ollama), analyzer, prompts, resources
│       ├── analyzer_factory.py         # LLMAnalyzerFactory (wires LLMProvider → LLMAnalyzer port)
│       └── providers/factory.py        # LLMProviderFactory
├── core/
│   ├── execution_engine/               # Instrument, market, question-driven executors
│   │   ├── factory.py                  # AnalysisExecutorFactory — builds executor list for DI
│   │   └── backtest/                   # Simple long-only executor wiring
│   ├── orchestrator/                   # ResearchOrchestrator, DefaultJobRunner, analyze runners
│   └── pipeline/tools/                 # ToolRegistry, tool executor, context tools, discovery/
│       ├── discovery/                  # PluginSpec bundles, entry points (copinance_os.tool_bundles), package scan
│       ├── bundles/                    # Scan targets: modules exporting tool_bundle_factory
│       ├── data_provider/              # Market & fundamental tools (cached provider calls)
│       └── analysis/market_regime/
│           ├── rule_based.py           # Trend, volatility, cycle tools
│           ├── indicators.py           # VIX, breadth, sector rotation tools
│           ├── macro_indicators.py     # FRED/yfinance macro aggregation
│           └── statistical.py          # HMM / regime-switching (stub — no registered tools yet)
├── data/
│   ├── analytics/options/              # QuantLib BSM Greek estimation
│   ├── cache/
│   ├── loaders/
│   ├── providers/                      # yfinance, FRED, market/ (option analytics), sec/edgartools
│   ├── repositories/
│   └── schemas/                        # MarketDataPoint coercion, price series
├── domain/
│   ├── backtest/                       # Deterministic simple long-only backtest models
│   ├── indicators/                     # Trend, volatility, oscillators, returns
│   ├── models/                         # Job, market, fundamentals, analysis requests, tool_bundle_context
│   ├── plugins/                        # PluginSpec, PluginRegistry
│   ├── ports/                          # Data, tools, execution, analytics, storage, tool bundles
│   ├── services/                       # Analysis report builders (instrument, market, question-driven)
│   ├── strategies/                     # Protocols for screening / valuation / future workflows
│   └── validation/
├── infra/
│   ├── config/                         # Settings (pydantic-settings)
│   ├── di/                             # Container, use cases, repositories, providers (wires AnalysisExecutorFactory)
│   └── plugins/                        # Load plugin specs (YAML/JSON) → callable registries
├── interfaces/
│   └── cli/                            # `__main__`→`main`; `dispatch/` (Typer vs generic research); `root` (lazy Typer); `commands/`; `shared/`
└── research/
    ├── reports/                        # Re-exports of domain report builders
    └── workflows/                      # analyze, market, profile, fundamentals, backtest use cases

Package layout (source)

AreaRole
copinance_os.domainEntities and value objects (Job, Stock, market types, profiles). Ports: data providers, repositories, storage, JobRunner, AnalysisExecutor, tools, analytics estimators, LLMAnalyzer. Strategy protocols for screening / valuation extensions.
copinance_os.research.workflowsApplication-facing use cases (analyze, market, profile, fundamentals, backtest). DTOs live in domain.models.analysis and domain.models.market_requests (re-exported from workflow modules).
copinance_os.core.orchestratorResearchOrchestrator: run_job and helpers. DefaultJobRunner resolves JobAnalysisExecutor.
copinance_os.core.execution_engineExecutors: deterministic instrument/market analysis, question-driven analysis.
copinance_os.core.pipeline.toolsTool implementations (market data, fundamentals, regime, macro), ToolRegistry, and discovery (build_data_provider_tool_registry, collect_question_driven_tools; context type in domain.models.tool_bundle_context).
copinance_os.dataProviders (yfinance, FRED; SEC/EDGAR in data.providers.sec via edgartools), cache, repositories, loaders, analytics (data.analytics.options for BSM Greeks).
copinance_os.ai.llmLLM provider adapters, configuration, prompt resources.
copinance_os.infra.diDependency injection: get_container(), lazy container proxy, singletons.
copinance_os.interfaces.cliConsole script → main: dispatch.parse_root_argv (Typer vs generic research), lazy Typer root, commands/*.
copinance_os.research.reportsRe-exports of domain report builders for research-facing imports.

Vocabulary

TermMeaningTypical code location
Analysis requestTyped input for progressive analyze flows (AnalyzeInstrumentRequest, AnalyzeMarketRequest); mode helpers (resolve_analyze_mode, execution_type_from_scope_and_mode).copinance_os.domain.models.analysis (also re-exported from research.workflows.analyze)
Analysis modeauto, deterministic, or question_driven; resolves how a job is built.domain.models.analysis + resolve_analyze_mode
JobNon-persisted execution envelope: scope, symbols, timeframe, execution_type.copinance_os.domain.models.job
Execution typeString discriminator for which executor runs (e.g. deterministic_instrument_analysis). Derive with execution_type_from_scope_and_mode.domain.models.analysis
ResearchOrchestratorFacade over JobRunner.run: the supported entry for running a constructed Job with context.copinance_os.core.orchestrator.research_orchestrator
AnalysisExecutorImplements one analysis style; validates and executes a Job.copinance_os.core.execution_engine
ToolCallable unit exposed to the LLM with schema and structured results.copinance_os.core.pipeline.tools, domain.ports.tools, domain.models.tool_bundle_context

Orchestration: what to call

  • Recommended: container.research_orchestrator() then await orchestrator.run_job(job, context). This matches how default analyze runners work.
  • Advanced: Implement the JobRunner port and pass it to ResearchOrchestrator(job_runner=...). The stock Container does not expose job_runner() as a getter — custom routing uses a custom orchestrator, a custom Container wiring, or set_container() (see Extending).

Container and performance

The global container from get_container() is a lazy proxy: resolving it (or accessors like container.research_orchestrator()) constructs the real Container once per process. Heavy dependencies (providers, executors) are created on first use, not at import time. The CLI keeps copinance --help fast by lazy-loading Typer subcommands (analyze, market, …) so those modules are not loaded until you run them.

Dependency injection and startup

Use get_container(...) from copinance_os.infra.di. Analysis executors are produced by AnalysisExecutorFactory.create_all (core.execution_engine.factory). LLM analyzers are created via LLMAnalyzerFactory (ai.llm.analyzer_factory).

See Using as a Library for get_container(...) options (LLM, FRED, cache, storage, prompts).

Extension points (ports)

Implementations live in data, ai, and infra; contracts live under copinance_os.domain.ports:

  • Data: MarketDataProvider, FundamentalDataProvider, MacroeconomicDataProvider, AlternativeDataProvider
  • Analytics: OptionsChainGreeksEstimator (default QuantLib BSM path)
  • AI: LLMAnalyzer
  • Execution: JobRunner, AnalysisExecutor
  • Persistence: StockRepository, AnalysisProfileRepository, Storage, cache backends
  • Tools: Tool, ToolSchema, ToolParameter; bundles: ToolBundleFactory / ToolBundleContext (domain.models.tool_bundle_context, domain.ports.tool_bundles)
  • Strategies: screening, due diligence, valuation, risk, thematic, monitoring (protocols for extensions)

Step-by-step: Extending. Interface listing: API Reference and src/copinance_os/domain/ports/.

Benefits

  • Testability — Ports are easy to fake; executors and tools are isolated.
  • Replaceability — Swap providers or LLM backends without changing domain rules.
  • Clarity — Numbers and series come from data and analytics layers; prose and routing from AI and CLI.