Follow these patterns when implementing quant domain resources like Dataset, Signal, Alpha, Portfolio, Strategy, Universe, Backtest, or MonitoringRun in OptAIC...
Guide for implementing domain resources that integrate with OptAIC's resource-based architecture.
Apply when:
OptAIC separates Definitions (plugins) from Instances (configs) from Runs (executions):
Definition (Plugin) Instance (Config) Run (Execution)
āāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā
BloombergPipelineDef ā SPX_OHLCV_Dataset ā PipelineRun (daily)
PortfolioOptimizerDef ā MVO_Conservative ā PortfolioOptimizationRun
MLModuleDef ā XGBoost_Predictor ā TrainingRun, InferenceRun, MonitoringRun
(none) ā BacktestInstance ā BacktestRun
Definitions: Reusable building blocks submitted as plugins (the "Law") Instances: Concrete configurations referencing definitions Runs: Executions that produce immutable versions and metrics
| Type | Purpose | Contains |
|---|---|---|
PipelineDef |
Data ingestion plugin | ETL code, schemas |
StoreDef |
Storage backend | Parquet/SQLite/Virtual |
AccessorDef |
Data access pattern | Simple/PIT/Field |
OpDef |
Math operator | REF, DELTA, MEAN |
OpMacroDef |
Saved expression | User formulas |
MLModuleDef |
ML model template | XGBoost, LSTM |
PortfolioOptimizerDef |
Optimization algo | MVO, HRP, RiskParity |
| Type | Parent Definition | Notes |
|---|---|---|
DatasetInstance |
PipelineDef + StoreDef + AccessorDef | Composition |
SignalInstance |
Inherits DatasetInstance | Promoted dataset |
ExperimentInstance |
OpDef/OpMacroDef | Expression config |
ModelInstance |
MLModuleDef | ML model config |
PortfolioOptimizerInstance |
PortfolioOptimizerDef | Optimizer config |
BacktestInstance |
None | Fixed procedure |
| Type | Parent Instance | Key Outputs |
|---|---|---|
PipelineRun |
DatasetInstance | rows_added, last_date |
ExperimentRun |
ExperimentInstance | preview_data |
BacktestRun |
BacktestInstance | equity_curve, trades, metrics |
PortfolioOptimizationRun |
PortfolioOptimizerInstance | weights |
TrainingRun |
ModelInstance | model_artifact |
InferenceRun |
ModelInstance | predictions |
MonitoringRun |
ModelInstance/DatasetInstance | drift_metrics, alerts |
Definition resource? ā Implements abstract interface, has test suite, requires evaluation Instance resource? ā References definition(s), has config, can be scheduled
Location: libs/db/models/<domain>.py
Link to resources table via FK. See references/db-patterns.md.
Location: libs/core/domain/<domain>.py
Use Pydantic. Never expose SQLAlchemy models to API. See references/dto-patterns.md.
Location: libs/core/domain/<domain>_service.py
Emit activities for all mutations. See references/service-patterns.md.
Update libs/core/resources.py ā ResourceType enum.
optaic db revision --autogenerate -m "add <domain> resource"
Location: libs/core/tests/test_<domain>.py
TYPE_CHECKING blocksDefinition.code_refThe code_ref field in Definition extension tables links to factory registration keys:
Definition.code_ref ā FACTORY.build(code_ref) ā Execution Object
Pattern: Service loads Instance ā loads Definition ā gets code_ref ā builds from Factory
See Service Patterns for implementation details.
Location: apps/api/routers/<domain>.py
| Router | Prefix | Key Endpoints |
|---|---|---|
ops.py |
/ops |
List operators, get details, evaluate expressions |
pipelines.py |
/pipelines |
Definition CRUD, instance CRUD, trigger runs |
experiments.py |
/experiments |
Create, run, update, save as macro |
datasets.py |
/datasets |
Get info, status, preview, refresh |
signals.py |
/signals |
Register, validate, promote, list |
from apps.api.deps import get_actor, get_db
from apps.api.rbac_utils import authorize_or_403, get_resource_or_404
from apps.api.services import DatasetService
@router.post("/{id}/preview", response_model=DatasetPreviewOut)
async def preview_dataset(
dataset_id: UUID,
payload: DatasetPreviewRequest = Body(...),
actor: ActorContext = Depends(get_actor),
db: AsyncSession = Depends(get_db),
) -> DatasetPreviewOut:
# 1. Get resource and check RBAC
resource = await get_resource_or_404(db, actor.tenant_id, dataset_id)
await authorize_or_403(db, actor, Permission.RESOURCE_READ, resource.id)
# 2. Call service (services emit activities, NOT routers)
service = DatasetService()
result = await service.preview(session=db, actor=actor, ...)
# 3. Return DTO (never SQLAlchemy models)
return DatasetPreviewOut(**result)
Location: apps/api/services/<domain>_service.py
| Service | Key Methods |
|---|---|
DatasetService |
get_status, preview, refresh |
SignalService |
register_signal, validate_signal, promote_signal, list_signals |
PipelineService |
submit_definition, deploy_definition, create_instance, trigger_run |
ExperimentService |
create_experiment, run_experiment, save_as_macro, update_experiment |
OpService |
list_operators, get_operator, evaluate_expression |
# apps/api/services/__init__.py
from apps.api.services.dataset_service import DatasetService
from apps.api.services.experiment_service import ExperimentService
from apps.api.services.op_service import OpService
from apps.api.services.pipeline_service import PipelineService
from apps.api.services.signal_service import SignalService
Location: apps/api/schemas.py (Quant Domain section)
| Schema Group | DTOs |
|---|---|
| Pipeline | PipelineDefinitionCreate, PipelineDefinitionOut, PipelineInstanceCreate, PipelineInstanceOut, PipelineRunOut |
| Dataset | DatasetPreviewRequest, DatasetPreviewOut, DatasetRefreshOut, DatasetStatusOut |
| Signal | SignalRegisterRequest, SignalOut, SignalValidateOut |
| Operator | OperatorOut, OperatorListOut, ExpressionEvaluateRequest, ExpressionEvaluateOut |
| Experiment | ExperimentCreate, ExperimentOut, ExperimentRunRequest, ExprimentRunOut, MacroSaveOut |
On application startup, the system bootstraps via libs/core/bootstrap.py:
from libs.core.bootstrap import (
SYSTEM_TENANT_ID, # 00000000-0000-0000-0000-000000000001
SYSTEM_SPACE_ID, # 00000000-0000-0000-0000-000000000002
SYSTEM_PRINCIPAL_ID, # 00000000-0000-0000-0000-000000000003
SYSTEM_TENANT_ROOT_ID, # 00000000-0000-0000-0000-000000000010
SYSTEM_PROJECT_ID, # 00000000-0000-0000-0000-000000000013
)
# bootstrap_system() creates (idempotent):
# 1. System Tenant
# 2. Admin Principal (admin@optaic.local)
# 3. TenantRoot Resource
# 4. Default role permissions (owner, operator, viewer, auditor)
# 5. System Space with Official + Staging sub-spaces
# 6. System Project for definitions
# 7. Admin owner role on System Space
TenantRoot
āāā Space (space_kind: personal|team|system)
āāā Subspace (subspace_kind: official) ā Production resources
āāā Subspace (subspace_kind: staging) ā Resources under review
āāā Subspace (subspace_kind: custom) ā User-created
āāā Project
āāā Resources (datasets, experiments, etc.)
apps/api/services/space_service.py provides:
class SpaceService:
async def create_space_with_subspaces(...) -> SpaceCreationResult
# Creates Space + Official + Staging sub-spaces
# Grants owner role to owner_principal_id
# Emits activities for all creations
async def create_user_with_personal_space(...) -> UserCreationResult
# Creates Principal + Personal Space
# Grants owner on Personal Space
# (Optionally grant VIEW on System Space)
async def create_team_space(...) -> SpaceCreationResult
# Creates Team Space with owner
# Optionally grants operator to members
Copy definitions from System Space to user projects:
# API: POST /resources/{resource_id}/copy
# SDK: client.resources.copy(resource_id, target_parent_id, new_name=...)
# Creates:
# 1. New resource with copier as owner
# 2. derived_from lineage edge to source
# 3. resource.copied activity
libs/sdk_py/admin.py provides admin operations:
# Create user with Personal Space
result = await client.admin.create_user_with_space(
display_name="Alice Smith",
email="alice@example.com",
)
# Returns: principal_id, space_id, official_subspace_id, staging_subspace_id
# Create Team Space
result = await client.admin.create_team_space(
name="Quant Research Team",
owner_principal_id=owner_id,
member_principal_ids=[member1, member2], # Optional
)