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
- Financial and quantitative work is deterministic — Implemented in domain models, data providers, analytics (QuantLib), and
core.execution_engineexecutors. Outputs are structured and testable. - LLMs do not replace computation — They explain, route questions to tools, and format answers. Tool implementations perform fetches and numeric work.
- Contracts over raw blobs — Prefer Pydantic models and explicit result types between layers; avoid passing untyped pandas frames across module boundaries.
- One front door for job execution —
ResearchOrchestratorwraps theJobRunnerport so use cases, CLI, and integrations do not scatter direct calls to executors. OverrideJobRunneronly 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 casesPackage layout (source)
| Area | Role |
|---|---|
copinance_os.domain | Entities 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.workflows | Application-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.orchestrator | ResearchOrchestrator: run_job and helpers. DefaultJobRunner resolves Job → AnalysisExecutor. |
copinance_os.core.execution_engine | Executors: deterministic instrument/market analysis, question-driven analysis. |
copinance_os.core.pipeline.tools | Tool 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.data | Providers (yfinance, FRED; SEC/EDGAR in data.providers.sec via edgartools), cache, repositories, loaders, analytics (data.analytics.options for BSM Greeks). |
copinance_os.ai.llm | LLM provider adapters, configuration, prompt resources. |
copinance_os.infra.di | Dependency injection: get_container(), lazy container proxy, singletons. |
copinance_os.interfaces.cli | Console script → main: dispatch.parse_root_argv (Typer vs generic research), lazy Typer root, commands/*. |
copinance_os.research.reports | Re-exports of domain report builders for research-facing imports. |
Vocabulary
| Term | Meaning | Typical code location |
|---|---|---|
| Analysis request | Typed 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 mode | auto, deterministic, or question_driven; resolves how a job is built. | domain.models.analysis + resolve_analyze_mode |
| Job | Non-persisted execution envelope: scope, symbols, timeframe, execution_type. | copinance_os.domain.models.job |
| Execution type | String discriminator for which executor runs (e.g. deterministic_instrument_analysis). Derive with execution_type_from_scope_and_mode. | domain.models.analysis |
| ResearchOrchestrator | Facade over JobRunner.run: the supported entry for running a constructed Job with context. | copinance_os.core.orchestrator.research_orchestrator |
| AnalysisExecutor | Implements one analysis style; validates and executes a Job. | copinance_os.core.execution_engine |
| Tool | Callable 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()thenawait orchestrator.run_job(job, context). This matches how default analyze runners work. - Advanced: Implement the
JobRunnerport and pass it toResearchOrchestrator(job_runner=...). The stockContainerdoes not exposejob_runner()as a getter — custom routing uses a custom orchestrator, a customContainerwiring, orset_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.