Saltar al contenido principal

RFC — Forecast / Scenario domain types (Sprint S6)

1. Motivation​

Track B / plan gap E11 and Phase 4 call for additive first-class types that today exist only as ad-hoc arrays, string fields, or pricing-context behaviour knobs:

Gap typeTodayPain
ForecastModel / ML / economic helpers return bare arrays or dictsNo shared identity, horizon, or measure semantics
CalibrationResultPricingContext.behaviour="Calibrated" is a string mode, not an entityCannot audit or replay a calibration
BenchmarkReferenced in analytics / promotion proseNo typed instrument or series handle
Scenariocurve_overrides / stress dicts on PricingContextNot addressable; cannot compose Simulation (ADR 034) cleanly
ListingInstrumentBase.primary_listing_venue: str | NoneInstrument×Venue product missing
Fill historyExecutionReport + legacy fills table + OrderFilled eventOpen plan question: additive Fill table vs enrich reports

This RFC freezes schemas and placement so Phase 4 can land additive modules without inventing parallel public APIs (ADR 033) or a second research→money path (ADR 037).

2. Non-goals​

  • Implementing dataclasses, ORM tables, migrations, MCP tools, or facade re-exports in this ticket.
  • Promoting these types into alphaswarm_core (wrong layer — ADR 033).
  • Collapsing Scenario into Simulation / Workflow / WorkRequest / DeploymentSpec (ADR 034).
  • Letting forecasts or scenarios emit venue orders; tradeable crossings remain OrderIntent / StrategyPromotionRequest (ADR 037).
  • Claiming unverified latency or “identical semantics” across engines (ADR 034).
  • Removing legacy Direction, orders/fills tables, or core/types.py shims (ADR 038 / inventory Phase 6).

3. Placement​

ConcernHomeNotes
Canonical typesalphaswarm.core.domain.{forecast,calibration,benchmark,scenario,listing}Mirror existing orders.py / positions.py style
Public re-exportalphaswarm.core.domain.__init__ + optional thin alphaswarm.facade.domainFacade = re-export only (ADR 033); no logic copy
Pricing measure enum (existing)alphaswarm.risk.pricing.measures.RiskMeasureStay in risk package; domain types reference it
Fill audit trail (existing)alphaswarm.trading.execution.ExecutionReport + execution_reportsEnrich / view; see §6
Persistence (later Phase 4)New additive Alembic migration(s) only if UI/MCP need durable rowsPrefer value objects first; tables when pickers/ledger require them

Do not place these types in alphaswarm_core.

4. Measure semantics (P vs Q)​

Forecasts and pricing must not silently share a probability measure.

Propose a small domain enum (name TBD at implement time):

MeasureSemantics
P # physical / real-world / predictive (statistical forecasting)
Q # risk-neutral / pricing measure
MIXED # explicit composite; requires documented components
UNSPECIFIED

Hooks:

TypeDefaultRule
ForecastPPredictive distributions; never used as a direct money-plane price without a pricing bridge
CalibrationResultQ (or calibrated market measure)Output of curve/surface fit consumed by PricingContext
BenchmarkUNSPECIFIED (usually market-observed under P)Clarify when used inside a Q pricing scenario
ScenarioRequired fieldStress under P vs pricing override under Q must be explicit
RiskMeasure / PricingContextRemains Q-orientedDomain Scenario may supply overrides into a PricingContext; it does not replace it

Forecast → money plane is forbidden. Path remains: forecast/advisory → validation → risk → authorization → OrderIntent (ADR 037).

5. Proposed type sketches (schemas only)​

Field lists are indicative; final dataclasses land in Phase 4.

5.1 Forecast​

Module: alphaswarm/core/domain/forecast.py

FieldType (sketch)Notes
forecast_idstrStable id for ledger / experiment stamp
instrument_idInstrumentId | NoneOptional when forecasting a series/index
series_refstr | NoneDataset / economic series handle when not an instrument
horizonstructured (e.g. bars / timedelta / event)Required
as_ofdatetimePIT issuance time
measure_semanticsMeasureSemanticsDefault P
pointDecimal | float | NonePoint estimate
distributionopaque payload or typed quantilesOptional; prefer structured quantiles over raw ndarray in the public type
model_refstr | NoneSpec / skill / predictor version id
experiment_id / test_idoptional FKsHard rule 34 when persisted
metadictExtensibility

Consumers: alphaswarm_models predictors, economic GdpForecast-style rows (bridge later), agent advisory tools (read-only via DataMCP when persisted).

5.2 CalibrationResult​

Module: alphaswarm/core/domain/calibration.py

FieldType (sketch)Notes
calibration_idstr
as_ofdatetime
behaviour"Calibrated" | "ConstraintsBased"Align with PricingContext
measure_semanticsMeasureSemanticsDefault Q
inputs_digeststrHash of constraint quotes / market snapshot
outputs_refcurve/surface ids or artifact URIOpaque to domain; storage elsewhere
qualitymetrics dict (rmse, arb gaps, …)No free-form “good enough” for promotion
pricing_context_run_idoptional linkTo existing PricingContextRunRow

Closes the gap where “Calibrated” is only a context flag.

5.3 Benchmark​

Module: alphaswarm/core/domain/benchmark.py

FieldType (sketch)Notes
benchmark_idstr
namestrHuman label
instrument_idInstrumentId | NoneWhen benchmark is a tradable/index instrument
series_refstr | NoneAlternate: external index series
currencyCurrency | None
measure_semanticsMeasureSemanticsUsually observed under P
metadict

Used by analytics, promotion evidence, and scenario relative-return definitions — not an order type.

5.4 Scenario​

Module: alphaswarm/core/domain/scenario.py

FieldType (sketch)Notes
scenario_idstr
namestr
measure_semanticsMeasureSemanticsRequired
as_ofdatetime | None
curve_overridesdictSame shape spirit as PricingContext.curve_overrides
surface_overridesdict
shock_setstructured listNamed shocks (spot, vol, rates, credit)
benchmark_idoptionalRelative stress
simulation_bindingoptional refThin link to GraphSpec/backtest config — not a new Simulation runtime (ADR 034)
metadict

Scenario is an Activity-input / Simulation-input value object, not a Workflow and not a money-plane command.

5.5 Listing​

Module: alphaswarm/core/domain/listing.py

FieldType (sketch)Notes
listing_idstr
instrument_idInstrumentId
venueVenueDomain venue VO
mic / symbol_at_venueoptionalVenue-local symbol
statusenum (active / halted / delisted)
is_primaryboolReplaces sole reliance on primary_listing_venue string
metadict

Additive beside InstrumentBase.primary_listing_venue; migration of string→Listing is a later strangler step, not a blocker for introducing the type.

6. Fills — enrich execution_reports, do not add a parallel domain Fills table​

Decision proposed by this RFC: treat ExecutionReport (report_kind = fill / partial fill) as the canonical fill history. Do not introduce a first-class domain Fill aggregate or a new fills domain table in Phase 4.

Rationale (evidence-backed):

  1. ExecutionReport already keys on (venue, venue_execution_id) and carries last_quantity, last_price, cumulative qty, commission, liquidity side — see alphaswarm/trading/execution/execution_report.py and orders hard-rule set (legacy fills is dual-write only).
  2. Domain already has event OrderFilled under alphaswarm.core.domain.orders.
  3. Plan open question #7; inventory / ADR 033 preference is Strangler Fig without duplicate ledgers.
  4. A separate Fill type would invite dual writers and break the “no new readers against legacy fills” rule.

Allowed later (still non-implementing here):

  • Optional typed alias / view DTO FillView that is a narrowed projection of ExecutionReport where report_kind indicates a fill — lives next to execution, or as a thin facade helper, not a second persistence root.
  • Additive columns on execution_reports if multi-partial reconstruction needs them (e.g. explicit fill_seq, leaves_qty) — single table enrichment only.

7. Direction.NET vs PositionSide.FLAT remapping hazard (from S5 inventory)​

Inventory §2 and §7 document a value conflict, not a rename:

Legacy (alphaswarm.core.types.Direction)Domain (PositionSide)Meaning
LONGLONGLong exposure
SHORTSHORTShort exposure
NET≠ FLATNET = netting / absolute-size accounting mode; FLAT = zero position

Forbidden: Direction.NET → PositionSide.FLAT automatic mapping.

Required remapping policy (implement with Phase 3–4 strangler, not in this RFC):

  1. Treat NET as an accounting mode (net book / absolute size), not a side.
  2. When converting positions: derive PositionSide from signed quantity (>0 LONG, <0 SHORT, ==0 FLAT); never from the NET token alone.
  3. Hotspots called out by inventory: backtest/broker_sim.py, strategies/execution.py — must get explicit adapter helpers before Symbol/Direction mass swap.
  4. Optional future enum PositionAccountingMode { GROSS, NET } may coexist with PositionSide; do not overload FLAT.

Forecast/Scenario types that express directional bias MUST use PositionSide or OrderSide, never legacy Direction.NET.

8. Migration phase​

StepWhenWork
This RFCS6Schemas + decisions only
Phase 4aAfter RFC acceptanceAdd domain modules + unit schema tests; re-export from core.domain
Phase 4bOptionalFacade re-export; DataMCP / EntityPicker only if persisted
Phase 4cOptionalAdditive migrations for Forecast/Scenario/Listing rows if product requires
Phase 3 overlapParallelDirection/PositionSide remapping helpers before strategy mass-migration (inventory)
Phase 5+LaterScenario binding to Simulation facade; golden replay may consume Scenario digests

Rollback: unused additive types; no forced cutover (plan Phase 4 rollback row).

9. Tests (when implemented — not now)​

  • Schema / round-trip tests under tests/core/domain/ for each new module.
  • Measure-semantics invariants: Forecast default P; rejecting unspecified Scenario.measure_semantics.
  • Explicit unit tests that Direction.NET mapping helpers do not yield FLAT.
  • Golden fixtures: ExecutionReport fill sequence reconstructs cumulative qty without a separate Fill table.
  • Boundary: no import of new domain forecast types from alphaswarm_controller / alphaswarm_cli.
  • ADR 037 guard: no test path from Forecast directly to venue submit.

10. Open questions​

  1. Persist Forecast/Scenario in Postgres in Phase 4 or keep value-object-only until a UI picker needs them?
  2. Should CalibrationResult own curve bytes or only digests + artifact URIs?
  3. Is FillView worth a public name, or is documenting ExecutionReport enough?
  4. Does Listing need temporal validity (list/delist windows) in v1?

11. Acceptance for this RFC​

  • Types proposed with homes under alphaswarm.core.domain
  • Fills decision: enrich / project execution_reports, no parallel Fill root
  • Direction.NET ≠ PositionSide.FLAT hazard documented with remapping policy
  • P vs Q measure hooks defined
  • ADRs 033 / 016 / 019 and S5 inventory cited
  • Status remains Proposed; no production type implementation in S6