Skip to content

โฑ๏ธ AI Export Technical Sampling

AI Export reduces historical numeric density deterministically while preserving scope, indicators, outputs, summaries, latest values, and selected events.

๐Ÿ“ Rational Bucket Formula

Let:

  • \(x\): whole-calendar-day distance backward from snapshot_as_of;
  • \(P\): shape exponent;
  • \(M\): half-transition offset after the seven-day daily ramp;
  • \(K\): asymptotic maximum bucket width in calendar days;
  • \(T\): total requested inclusive calendar-day count.

The continuous width is:

\[ f(x;P,M,K) = 1+(K-1) \, \frac{\max(x-7,0)^P} {M^P+\max(x-7,0)^P} \]

The integer width is:

\[ D(x;P,M,K) = \max\left( 1, \operatorname{round}_{\text{half-up}}\left[f(x;P,M,K)\right] \right) \]

Boundaries are generated iteratively:

\[ x_0=0 \]
\[ x_{n+1}=\min\left(T,\ x_n+D(x_n;P,M,K)\right) \]

Each offset interval becomes one bucket when translated back from snapshot_as_of. Runtime arithmetic uses Decimal with explicit ROUND_HALF_UP, not binary floating point or banker's rounding.

The result is deterministic, positive, monotonic in historical width, bounded by \(K\), gap-free, overlap-free, and clipped exactly to \(T\).

๐Ÿ“… Recent Daily Window

For every detail level and temporal class:

\[ 0 \le x \le 7 \Rightarrow D(x)=1 \]

The most recent seven calendar days therefore remain daily. This is calendar sampling, not market-session sampling. Empty intervals remain in bucket plans and diagnostics. Indicator payloads publish source/non-empty counts but omit empty rows; the public renderer also omits completely empty financial temporal rows. Neither path invents observations or converts absence to zero.

๐Ÿงฎ Full Policy Matrix

Reference counts use exact inclusive periods of 90, 180, and 365 calendar days.

Detail Temporal class P M K 90d 180d 365d
Compact Very Fast 2 30 30 20 23 29
Compact Fast 2 25 35 18 21 26
Compact Medium Fast 2 20 42 16 18 23
Compact Medium 2 10 42 14 16 20
Compact Slow 2 5 49 12 14 17
Compact Very Slow 2 5 84 11 12 14
Standard Very Fast 2 30 14 26 33 46
Standard Fast 2 21 15 23 29 41
Standard Medium Fast 2 20 17 21 26 37
Standard Medium 2 15 20 18 23 32
Standard Slow 2 10 22 16 20 28
Standard Very Slow 2 5 28 13 16 23
Full Very Fast 2 30 7 35 49 75
Full Fast 2 28 8 32 44 67
Full Medium Fast 2 23 9 28 38 59
Full Medium 2 16 10 24 33 51
Full Slow 2 10 11 21 29 46
Full Very Slow 2 9 14 18 24 38

Within one detail level, density decreases from Very Fast to Very Slow. For one temporal class, Full is at least as dense as Standard, which is at least as dense as Compact. Very Fast is the original detail-only baseline.

๐Ÿ’น Price and Rate Policy

Asset prices, FX rates, and equivalent reference series use detail only:

Detail P M K
Compact 2 30 30
Standard 2 30 14
Full 2 30 7

Signal temporal classes never change reference price/rate density.

๐Ÿ“ˆ Indicator Policy

Indicator history uses:

detail_level + plugin-owned temporal_class โ†’ P, M, K

AI Export does not branch on signal codes. The plugin resolves one temporal class from normalized parameters; central policy resolves the matrix row.

๐Ÿ—‚๏ธ Initial Instance Mapping

The Asset bundle has 20 instances. Portfolio and Broker reuse it per eligible asset. FX uses the marked 12-instance subset.

Instance Signal Temporal class In FX
ema_20 EMA(20) Medium โœ“
ema_50 EMA(50) Slow โœ“
ema_200 EMA(200) Very Slow โœ“
sma_50 SMA(50) Slow โœ“
sma_200 SMA(200) Very Slow โœ“
kama_20 KAMA(20) Medium โœ“
aroon_25 Aroon(25) Medium
adx_14 ADX(14) Medium
donchian_20 Donchian(20) Medium Fast
rsi_14 RSI(14) Very Fast โœ“
macd_12_26_9 MACD(12,26,9) Medium Fast โœ“
ppo_12_26_9 PPO(12,26,9) Medium Fast โœ“
roc_20 ROC(20) Fast โœ“
stoch_rsi_14_3 StochRSI(14,3) Very Fast โœ“
cci_20 CCI(20) Fast
bollinger_20_2 Bollinger(20,2) Medium Fast โœ“
atr_14 ATR(14) Fast
natr_14 NATR(14) Fast
mfi_14 MFI(14) Very Fast
obv OBV Medium

EMA and SMA use exact parameter-match rules because one plugin produces multiple time horizons. Other listed plugins use one fixed class for their current curated instances. Unknown or ambiguous matches raise; official plugins do not silently fall back.

๐Ÿงบ Multi-output Indicators

MACD, PPO, Bollinger, Donchian, Aroon, StochRSI, and ADX produce multiple outputs. Every output of one instance shares the same bucket boundaries. Scalar outputs and band components become columns in one row-oriented table.

For each populated cell:

  • one observation uses compact value + real date;
  • multiple observations preserve count, first, min, max, last, and their real observation dates.

period_summary is calculated across the entire exported period, independent of bucket density. Each output column also carries its latest observed value/date.

๐Ÿ“ฆ Public Indicator History Limits

The backend applies one additional deterministic presentation limit to non-empty indicator buckets for each entity and Signal instance:

Detail Non-empty rows
Compact 5
Standard 10
Full All

Compact and Standard select rows uniformly across the full exported period, including the first and last non-empty rows. This does not change the Signal set, Asset scope, output columns, latest values, or full-period summary. The public manifest exposes indicator_history_row_limit; each indicator payload retains source bucket and source non-empty row counts.

The compact text renderer uses row-relative date references inside history cells: s is the row start, e is the row end, and +N is a calendar-day offset from the row start. Event values are positional only when their definition declares the exact value_fields order. These are lossless text encodings, not additional financial calculations.

๐Ÿ”ฅ Calculation Range, Exported Range, and Warm-up

Indicators calculate on sessions โ€” the quote days of a warm-up-inclusive input โ€” with plugin-owned, parameter-aware warm-up. The loaded series keeps one point per calendar day. A day filled with an earlier price or rate carries backward_fill_info, and an Asset's stored weekend or market-holiday row that repeats the previous close exactly is marked as carried before the signals run (mark_market_closed_carries, the rule the Risk series use). For every plugin with computes_on_quote_days โ€” the 17 technical indicators, so every curated instance โ€” SignalService keeps only the quote days of the selected points (quote_day_points): points whose price was quoted on their own date, even when their exchange rate was carried. Periods, warm-up, minimum history, and visible points therefore count sessions, and indicator values are dated on sessions. States and annotations are also derived before numeric bucket aggregation.

Only the requested exported period is serialized:

warm-up-inclusive calendar input
โ†’ keep its sessions (quote days)
โ†’ full session-level indicator and event calculation
โ†’ slice to exported period
โ†’ aggregate numeric history

The load window is counted in calendar days: SignalExecutionPlan.max_history_days_before_visible is the largest warm-up of the requested instances, in sessions, times SESSION_WARMUP_DAY_MULTIPLIER (two calendar days per session). Asset loading delegates warm-up to SignalService through AssetSourceManager, which loads that window before the requested start. FX sizes its own load of earlier daily rates with the same plan value (_fx_warmup_days); a day without a published rate is backward-filled, carries backward_fill_info, and is not a session. The component response declares warmup_policy: component_owned; the current aggregate calculation_range and earliest_calculation_date metadata fields are not populated.

FX source history may begin after the requested start. Dates before the first stored rate are omitted from the warm-up input rather than failing the entire snapshot. SignalService then includes calculable ok/partial instances and reports unavailable instances through technical coverage. Coverage stays on the calendar although indicators count sessions: the snapshot metadata publishes requested/available ranges and calendar-day coverage, with the counts of observed and backward-filled days. No future rate is ever used.

๐ŸŽฏ Event Selection

Events are validated, observed-only filtered, epsilon checked, gap checked, and semantically deduplicated before selection. They are detected from original observations, never from buckets.

Policy is independent for every:

entity_id + annotation_key

For Portfolio and Broker, entity_id retains the originating Asset identity before events are merged. FX uses the canonical pair identity.

The complete recent window and minimum latest count are detail-owned:

Detail Complete recent window Minimum latest events
Compact 7 calendar days 3
Standard 21 calendar days 10
Full 30 calendar days 20

For each group:

  1. sort newest to oldest;
  2. include every event satisfying the detail-owned inclusive recent boundary;
  3. if fewer than the detail-owned minimum are included, continue backward to that minimum;
  4. if fewer events exist, include all;
  5. restore deterministic public chronological order after selection.

This is not a hard cap. A volatile annotation may export more than the minimum when more events occur inside the complete recent window. There is no ranking, relevance score, family quota, or episode consolidation.

Each group exports a selection summary with detected/recent/exported counts, selection status, oldest/newest detected and exported dates, and optional upward/downward counts.

๐Ÿงญ Focused Context Policies

The complete technical datasets and explicitly technical analyses continue to use the global policy above unchanged.

Semantic Composition V2 also exposes separate focused context components for financial analyses. Their backend-declared policies are intentionally narrower and do not alter global Compact/Standard/Full:

  • Portfolio/Broker general snapshots expose current/summary market fields, a 6/12/24-row observed-only uniform path per eligible Asset, and the latest selected structural event per category;
  • multi-Asset comparisons expose a bounded set of latest structural event types;
  • Portfolio Description uses an aggregate 30-day event digest, with the latest prior event when a type has no recent occurrence;
  • Asset Position Context exposes 8/16/30 uniform observed rows;
  • FX Market Context exposes 8/16/30 uniform genuine-observation rows;
  • Portfolio/Broker aggregate NAV paths expose 8/16/30 uniform rows. NAV values are explicitly flow-inclusive; normalized return uses historical TWRR;
  • focused rows carry bucket boundaries, the real representative observation, observation count, normalized index, and return from the first observation. Bucket extrema are omitted because the per-entity summary already carries full-period extrema;
  • Drawdown context sections carry no numeric history at all: they reuse the Risk drawdown_summary current/maximum episode dates and coverage verbatim;
  • PAC's per-Asset Drawdown snapshot is one observed-price row per Asset and also carries no Drawdown history;
  • technical coverage names current Assets excluded from technical eligibility and publishes a deterministic reason such as end_value_unavailable;
  • every detailed indicator block publishes its own result_status and partial_reason_code;
  • task-specific evidence datasets apply component-local monotonic detail (for example income Compact aggregates by (month, asset, income_type), Standard adds bounded recent dated rows, Full exposes every dated row).

These limits are semantic component contracts, not token-triggered truncation. They do not change the Signal or Asset scope; Full retains every non-empty indicator bucket and the complete 30-day/minimum-20 event policy.

๐Ÿงน Public Empty Temporal Rows

The generic public renderer applies the same no-empty-row principle to financial temporal tables that technical histories already use.

A row is omitted only when it contains nominal start/end/index metadata and no observation, flow, variation, P&L, extrema, reconciliation, meaningful observed date, or explicit non-absence status. Numeric zero remains meaningful and keeps the row.

This presentation rule does not modify:

  • backend bucket boundaries;
  • calculations or requested frequency;
  • sampling policy;
  • requested/effective/available periods;
  • coverage or insufficient-history warnings.

Probe diagnostics count empty rows detected/omitted and temporal rows remaining.

๐Ÿท๏ธ Latest-per-category Context Events

Focused market-context components select at most one latest event per (entity_id, signal_category). The category is plugin-owned (read from the Signal plugin's category, failing loudly for unknown plugins); candidates are the allowlist-filtered observation-level discrete events. Tie-breaks within a category are deterministic by (date, annotation key, signal code), and categories with no eligible event are omitted rather than emitted as null rows.

This is distinct from the technical event policy above, whose recent window and minimum count depend on detail. The permanent density probe therefore tracks these event families separately: detailed_event_rows (complete-policy events), context_event_rows, latest_event_rows with latest_event_category_count, and the Portfolio Description event_digest_group_count / event_digest_underlying_event_count.

๐Ÿ’ฑ FX Observed Range Position

FX conversion-timing evidence frames the current rate against the observed period minimum/maximum as a plain range position (current - min) / (max - min), deliberately not a historical percentile or rank and not a forecast. A flat range (observed_maximum == observed_minimum) or an empty period reports range_position_ratio = None with an explicit reason (flat_observed_range / no_observed_rates_in_period). Partial source history is explicit through is_partial_history plus a partial_history_reason (source_history_starts_after_period_start / no_genuine_observations_in_period); only genuine, non-backfilled observations count and no future rate is ever consulted.

The same timing component (fx.timing_context) also publishes trailing 30- and 91-day returns, the period return, and daily-return volatility, all from genuine observations inside the AI period. A trailing return is null when the observed history does not reach back to its anchor date.

๐Ÿงพ Manifest Examples

The public prompt manifest exposes only information that helps interpret the sampled data:

technical_sampling:
  detail_level: standard
  indicator_history_row_limit: 10
  price_policy:
    bucket_count: 46
  indicator_policies:
    - signal_instance_id: ema_200
      signal_code: EMA
      temporal_class: very_slow
      bucket_count: 23

detail_level appears once because every policy in one request shares it. temporal_class explains the indicator horizon, while bucket_count reports the backend aggregation density. indicator_history_row_limit reports the additional non-empty rows retained per entity/instance in the public payload.

P, M, and K remain normative internal policy parameters. They stay in the matrix, formula tests, mathematical probes, and engineering reports above, but are intentionally absent from the API response and copied prompt.

Top-level event policy audit:

event_selection:
  minimum_latest_events_per_annotation: 10
  complete_recent_window_days: 21
  grouped_by:
    - entity_id
    - annotation_key

Per-group payload summary:

entity_id: asset:42
annotation_key: ema_50_ema_200
detected_count: 37
recent_window_count: 6
exported_count: 6
selection_applied: true
oldest_detected_event_date: 2024-02-01
newest_detected_event_date: 2026-07-29
oldest_exported_event_date: 2026-07-18
newest_exported_event_date: 2026-07-29

๐Ÿงช Extension and Testing Checklist

When adding or changing an exportable indicator:

  1. declare plugin-owned ai_export_temporal_rules;
  2. use fixed rules only for one semantic horizon;
  3. use explicit parameter matching for multi-horizon plugins;
  4. verify exactly one rule resolves for every curated instance;
  5. keep warm-up, input validation, output validation, AI descriptions, states, and annotations plugin-owned;
  6. verify all outputs share one grid;
  7. test all relevant detail/class matrix rows and 90/180/365 counts;
  8. test daily first seven days, monotonic widths, \(K\) bound, exact final boundary, no gap, and no overlap;
  9. test unavailable/failed omission without sibling loss;
  10. test detail-owned event boundary inclusivity/minimums, unlimited events inside the complete recent window, grouping, deduplication, and order;
  11. inspect technical_sampling, event_selection, and selection summaries;
  12. run documentation validation.
./dev.py test services ai-export
./dev.py test services signal-plugin-matrix
./dev.py mkdocs build
./dev.py mkdocs check-links