Skip to content

Framework vs User Control

This document is the canonical strategy contract for StackSats.

Framework (Non-Negotiable)

  1. Given a fixed budget: generalized to a proportion (total accumulation weight = 1).
  2. Given a fixed allocation span (global config) between 90 and 1460 days (inclusive).
  3. Defines a complete strategy that:
  4. Initializes all daily accumulation weights over the allocation span such that the sum of all weights equals the total budget (=1).
  5. Makes day-by-day iterative updates to future weights, reshuffling the fixed budget.
  6. Locks historical weights (past days are immutable).
  7. Enforced budget management:
  8. Total budget must be used by the end of the allocation span (meaning all daily weights must sum to 1).
  9. Enforced daily accumulation:
  10. Minimum daily weight must be greater than or equal to 1e-5.
  11. Enforced steady accumulation:
  12. Maximum daily weight must be less than or equal to 0.1.
  13. No Forward-Looking Data:
  14. Use only current and past data—no peeking into the future.
  15. Validation guards (NaN/inf/range checks) and final invariants.

WeightTimeSeries is the output object that enforces these invariants: required columns (date, weight, price_usd), weight sum = 1, non-negative and finite weights, daily weight within min/max bounds, date contract, and optional locked/day_index. Input features are provided as FeatureTimeSeries (schema and time-series validation, including optional no-forward-looking checks).

User Owns (Flexible)

  1. Feature engineering from lagged/base data
  2. Signal definitions/formulas
  3. Signal weights and hyperparameters
  4. Stable strategy identity/config surfaced through metadata(), params(), and spec()
  5. Daily intent output:
  6. propose_weight(state) for per-day proposals, or
  7. build_target_profile(...) for a full-window intent series

When both intent hooks are implemented, intent_preference must be set explicitly. Ambiguous dual-hook strategies are rejected by contract validation.

Handoff Boundary

The user never writes the framework iteration loop.

User output (proposed_weight_today or daily profile intent) is handed to the framework allocation kernel, which computes final_weight_today by applying:

  1. feasibility clipping
  2. remaining-budget rules
  3. historical lock rules
  4. final invariant checks

Required Behavior

  1. Users can strongly influence allocation each day through features/signals/intent.
  2. Users cannot alter iteration mechanics or rewrite past allocations.
  3. Local, backtest, decision, and production-integrated execution run the same sealed allocation kernel.
  4. Hard-required transformed columns should be declared via required_feature_columns().
  5. Durable strategy configuration should come from params(), not runtime caches.

Production Daily Lifecycle

Canonical agent-native entrypoint: stacksats strategy decide-daily. Hosted agent-native surface: stacksats serve agent-api.

  1. Load locked historical weights for the active allocation span.
  2. Build lagged features/signals using information available up to current_date.
  3. Collect user daily intent (proposed_weight_today or profile-derived intent).
  4. Project to feasible final_weight_today with remaining-budget constraints.
  5. Emit a structured decision payload for an external agent.
  6. Either hand that payload to an external agent directly or serve it over the hosted /v1/decisions/daily HTTP surface.
  7. External brokerage execution happens outside StackSats.
  8. External agents report execution receipts back through /v1/executions/receipts when using the hosted service.
  9. Persist today as locked.
  10. Advance to next day; past values remain immutable.

Integrated convenience path: stacksats strategy run-daily. Use it when you intentionally want StackSats to submit through an execution adapter after generating the same validated decision.