diff --git a/.github/workflows/preflight.yml b/.github/workflows/preflight.yml index 8108309b..4be81857 100644 --- a/.github/workflows/preflight.yml +++ b/.github/workflows/preflight.yml @@ -6,12 +6,15 @@ name: Release Preflight # CodeQL and benchmark gates, while there is still nothing to take back. # # Which commit it validates, and how it refuses to validate the wrong one: -# * A push to the v4.1.0 candidate branch, work/v4.1.0-release-hardening: the +# * A push to the v4.2.0 candidate branch, work/v4.2.0-release: the # commit pushed, and only that -- the run's commit must be the pushed tip # (github.event.after), the ref must be that branch, and a deletion is # refused: when a branch is deleted GitHub reports the DEFAULT branch's # commit for the run, which is how a push trigger could validate `main`. -# This route needs nothing on `main`. Retarget or remove it after v4.1.0. +# This route needs nothing on `main`. Retarget or remove it after v4.2.0. +# (v4.2.0's development branch is work/v4.1.1-future-readiness, named before +# the number was chosen; a candidate pushed for release goes to +# work/v4.2.0-release, or is validated by labelling its pull request.) # * Run manually ("Run workflow"): the commit of the branch or tag chosen under # "Use workflow from". The `candidate` input must repeat that name, so a run # left on `main` by mistake fails in its first step instead of validating @@ -34,13 +37,13 @@ on: required: true type: string expected_version: - description: "The version this release will be, e.g. 4.1.0 (its tag is v)" + description: "The version this release will be, e.g. 4.2.0 (its tag is v)" required: true type: string # The candidate branch alone; never main. The guard below names it again. push: branches: - - work/v4.1.0-release-hardening + - work/v4.2.0-release pull_request: branches: - main @@ -78,7 +81,7 @@ jobs: REF_NAME: ${{ github.ref_name }} RUN_SHA: ${{ github.sha }} # The branch on.push.branches names; a test holds the two together. - PUSH_BRANCH: work/v4.1.0-release-hardening + PUSH_BRANCH: work/v4.2.0-release PUSHED_SHA: ${{ github.event.after }} PUSH_DELETED: ${{ github.event.deleted }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} @@ -138,7 +141,7 @@ jobs: python-version: "3.12" # The candidate must BE the release, not merely agree with itself: a tree - # whose every reference says 4.1.1 fails here. A manual run names the + # whose every reference says 4.2.1 fails here. A manual run names the # release in expected_version; a push or a labelled pull request is the # candidate branch's release, RELEASE_VERSION (a test holds it to the # package's version and to the push branch's name). @@ -147,7 +150,7 @@ jobs: env: EVENT: ${{ github.event_name }} DISPATCHED_VERSION: ${{ inputs.expected_version }} - RELEASE_VERSION: "4.1.0" + RELEASE_VERSION: "4.2.0" run: | set -euo pipefail if [ "${EVENT}" = "workflow_dispatch" ]; then diff --git a/CHANGELOG.md b/CHANGELOG.md index bc995170..4f2396dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,727 @@ changed. The current state of the project is in `README.md`, `ROADMAP.md` and --- +# [4.2.0] - unreleased (the development target the maintainer approved; not yet published or tagged) + +**A researcher-defined strategy over several assets, datasets, sentiment, macro +releases and fitted models -- every timed input applied at its instant on every +driver, contracts that expire, are exercised, assigned and delivered by their +declared terms in any market a calendar describes, bonds that pay coupons and +redeem, orders worked in children by execution algorithms over historical bars, +resting limit orders that fill only once a declared queue model puts them at the +front of their price level, a strategy configuration read back as its declared +type, settlement-date cash, margin calls and forced liquidation on declared +margin terms, a paused replay that keeps all of it durably -- and a +source-derived audit of what +AlphaLab supports from research to production.** + +Additive on the v4.1 contract: against v4.1.0's manifest it adds names, gives +existing functions keyword-only parameters with no effect unless given, adds +`AssetType.BOND`, `AssetType.INDEX` and `ExecutionRouting.ALGORITHMIC`, and +removes or re-signs nothing incompatibly. Three schemas step, +each reading every earlier payload: instrument 3 -> 4 (a last trading instant +and contract terms), replay 1 -> 2 -> 3 (the schedule's digest, an execution +plan's book and a queue model's book; then the instrument registry's digest) +and live 2 -> 3 (the registry's digest). No run, pipeline or portfolio schema +moves, and a run of v4.1.0's inputs has the identity it had. Because it adds +public API it is a minor release under `docs/api/PUBLIC_API.md`'s policy -- see +`docs/audit/FUTURE_READINESS_V4_1_1.md`. ADR-0051 records the decisions; ledger +IDs are in brackets. + +## Added + +Entries marked *phase 3* were delivered on the candidate branch after its +validation at `350f3f7` (`docs/audit/V4_2_0_READINESS.md`, section 10); those +marked *phase 4* after its phase 3 checkpoint at `bbc4085` (section 11). + +Entries marked *phase 5* were added on the same development branch after the +phase 4 checkpoint at `cde89f6` (section 12), and those marked *phase 6* after +the phase 5 checkpoint at `c348e69` (section 13). **The maintainer assigned +phases 3 to 6 to v4.2.0** (section 14). + +* **A resumed run's registry held to its terms, not its type** [FUT-041] + (*phase 6*). `alphalab.instrument.snapshot.registry_digest` names a + registry's terms; a paused replay's and a live run's payload record it + (`ReplaySnapshot.instruments_digest`, `LiveRunSnapshot.instruments_digest`; + replay and live schemas 2 -> 3, each read through its schema history), and + their restores refuse a registry of the right type with other borrow, + margin, control, settlement, tick or contract terms. `run_snapshot.restore`, + the pipeline's `restore` and `restore_checkpoints` take a keyword + `instruments_digest` to hold a bare run to a manifest's. + `live_snapshot.restore(registry_changed=True)` continues a live run under + reference data the host updated. `alphalab.runtime.registry_check` + (`RegistryVerification`, `registry_verification`, `verify_registry`) reports + a payload written before phase 6 `UNRECORDED`: restored by type, as before, + and not called verified. No `result_id` moves. +* **Auction books and indications delivered during a run** [FUT-043] + (*phase 6*). `alphalab.instrument.DeliveredAuction` -- a book or an + indication, its knowledge instant, source and version -- is delivered by + `RunSchedule.of(auctions=)`, `ExecutionPipeline.deliver_auction`, + `RunEngine.deliver_auction` and `LiveSession.deliver_auction`, which records + one as known when it arrived and refuses one from the future. It is kept on + `MarketState.auctions` (written only when delivered) and read by the uncross + and by `context.universe.auction` beside what was declared, by + `alphalab.instrument.controls.published`'s rule. `context.universe.reopening` + gives the held auction's result. +* **A manifest names the schedule that delivered what the run kept** [REP-009] + (*phase 6*). `manifest_for_run` refuses a manifest naming no schedule, or + one without them, for a run that kept delivered surfaces or auction + publications. +* **The phase 6 engine integration audit** (*phase 6*, + `docs/audit/ENGINE_INTEGRATION_AUDIT_PHASE6.md`): the live boundary and an + adapter's requirements, every economic input against what names it, the + research engine against tests; FUT-044 to FUT-046 proposed. +* **The default manifest's registry: the options** [FUT-042] (*phase 6*). + ADR-0052, *proposed*: keep it opt-in (A), make it mandatory (B), or version + the manifest identity (C, recommended), with what each does to every + manifest and certification already made and a migration plan. Nothing + changes until the maintainer decides; `instruments=True` works as before. +* **A rerun from a manifest compared like for like** [REP-008] (*phase 6*). + `rerun_from_manifest` recorded the rerun's manifest without the registry or + the schedule, so a manifest naming either never reproduced; it now names the + registry as the original does and takes keyword `schedule` and `execution`. + +* **An elective action's late delivery amended by the issuer's word** [ACC-018] + (*phase 5*). A `DeliveryAmendment` of an `ElectiveExchange`'s leg delivered + later (FUT-038) was refused at declaration, as if undeclared; it now delays, + corrects or fails the leg as for a `DeliveredExchange` (FUT-037). +* **A strategy's view of what a venue publishes of an auction** [FUT-040] + (*phase 5*). `TradingControls(indications=)` declares a venue's + `alphalab.instrument.AuctionIndication`s -- an indicative price, a matched + quantity and a signed imbalance, each only where published, or a withdrawal + -- at their publication instants (written only when declared). + `context.universe.auction(asset_id)` returns a `PublishedAuction` while a + reopening auction is pending: the book and indication last published since + the halt began and by the strategy's instant, their ages, a withdrawal as + one, an extension's instant; `None` otherwise. Never a later publication, + the run's uncross or depth the venue did not publish; an indication never + moves the uncross. `ExecutionAssumptions.venue_conditions` states declared + indications. +* **The engine integration audit** (*phase 5*, + `docs/audit/ENGINE_INTEGRATION_AUDIT_PHASE5.md`). `assess_reproducibility` + reports, as a finding, a run manifest naming its instrument registry by type + alone -- a rerun under other terms whose record agreed reproduces it -- and + certification carries it among its limitations; no outcome and no manifest + identity moves (REP-007). `ExecutionAssumptions.venue_conditions` states each + venue condition a run's registry declared -- bands, halts, auctions and the + books they uncross from, margin, borrow, tick and settlement terms -- as the + caller's declaration, not verified against a venue; not one of `settings()`, + so no identity moves (EXE-014). `LiveSession.deliver_surface` records a + surface as known when the session received it (LIV-002). FUT-041 to FUT-043 + proposed. +* **Volatility surfaces arriving as data during a run** [FUT-039] (*phase 5*). + `RunSchedule.of(surfaces=)` delivers `alphalab.risk.ObservedSurface`s -- + an options surface, its knowledge instant, source and version -- kept on + `RiskState.surfaces` as they arrived and persisted with it (written only + when delivered). `ScenarioMargin(surface_feed=, surface_max_age=)` reads + them: the one known by the check and as of the latest instant, a revision + over what it revises, never a late older arrival or a stale one, resolved + by the execution path into `OptionRisk.volatility`; where none is given the + option is margined by its own terms. `context.risk_view.volatility_surface` + shows a strategy the latest known. `ExecutionPipeline.deliver_surface`, + `RunEngine.deliver_surface`; `alphalab.instrument.margin.read_surface`. +* **An elective action's terms corrected after their announcement** [FUT-038] + (*phase 5*). `ElectiveExchange(corrections=)` declares the issuer's + `TermsCorrection`s, each a version of its own (`ElectiveTerms`, + `ElectiveExchange.versions`, `terms_at`, `final`) in effect from its + announcement and recorded then as an `alphalab.strategy.TermsAnnounced`. Each + election is read against the version in effect when it was made, within its + window -- a reopened deadline counts none made while closed -- and kept or + voided as the correction declares (`EarlierElections`); the holding is + exchanged by the version in effect at the action's instant, its payment and + its deliveries (`ElectiveExchange.deliveries`, FUT-037) with it. +* **Legs delivered after an exchange's effective instant** [FUT-037] (*phase 5*). + `alphalab.backtesting.DeliveredExchange` declares each leg's delivery + instant and an `alphalab.portfolio.DeliveryConvention`: the entitlement is + booked at the effective instant and its units owed until delivered -- an + `EXCHANGE` ledger entry of its own, pending in the settlement index + (`PendingSettlement.delivery`) -- by default neither sold nor counted as + margin collateral before they arrive. `DeliveryAmendment` delays, corrects + (a partial delivery) or fails one, a shortfall writing off its units and + their share of the basis from each strategy by its part + (`PortfolioEngine.amend_delivery`, `ExecutionPipeline.amend_delivery`, + `RunEngine.amend_delivery`, the `DeliveryAmended` event). + +* **Options repriced from a dated volatility surface, over a horizon** [FUT-032] + (*phase 4*). `ScenarioMargin(surfaces=, horizon=)`: options + `VolatilitySurface`s by underlying, dated by their as-of instant, and the + seconds each scenario is valued after the margined instant. An + `OptionPricing(volatility=None, ...)` reads the surface as of the margined + instant at its strike and expiry through `implied_vol_across_expiries`; + where it refuses -- beyond the quotes, a calendar arbitrage -- the option is + margined by its own terms. Surfaces and the horizon are written to the + registry snapshot only when declared; a model without them keeps its form. +* **Elections split between holders, and proration** [FUT-030] (*phase 4*). + An `ElectiveExchange` exchanges each holder's share by its own election, a + part election (`metadata['units']`, recorded as + `alphalab.strategy.UnitsElected`) electing that many units; the default takes + what is not elected, or elected beyond the holding. `proration` declares the + issuer's factor for an oversubscribed alternative, the default taking the + rest; `record_at` records each holder's holding then + (`alphalab.strategy.HoldersRecorded`) and bounds its election by it. Each + alternative is applied to its electors' part through + `apply_exchange(part=)`, the basis apportioned by units, fractions paid in + lieu per part. Under FUT-024 holders electing differently all took the + default; one holder, or holders agreeing, are unchanged. +* **A buy-in's premium, and borrow located per strategy** [FUT-035] (*phase 4*). + `BorrowAvailability(buy_in_premium=, buy_in_fee=)`: the lender's premium on + a buy-in's value, booked as a fee with each fill of the cover, and a fixed fee + per buy-in order with its first, attributed to the strategies bought in. + `BorrowAvailability(allocations=)`: the units located for each strategy, out + of what is available and never more; each strategy draws beyond its own only + on what is shared, first come, a strategy's resting shorts counted; a recall + buys each in for its own excess. `alphalab.runtime.borrow_claims` and + `BorrowClaim` attribute the book's borrow, and its fee, to each strategy. +* **A corporate action's payment amended by the issuer** [FUT-034] (*phase 4*). + `alphalab.backtesting.PaymentAmendment` and `RunSchedule.of(payments=)`: a + `PaymentChange` -- `DELAYED`, `FAILED`, `CORRECTED`, `REVERSED`, + `REPLACED` -- to one currency's payment of a declared exchange, booked by + `RunEngine.amend_payment` / `ExecutionPipeline.amend_payment` / + `PortfolioEngine.amend_payment` as a ledger entry of its own and a + `PaymentAmended` event. A failed payment is never settled cash; a delayed + one funds nothing until paid; a correction is per unit exchanged; a + replacement pays in another currency. `alphalab.portfolio.settlement.entitlements` + reconciles each payment from the ledger as of any instant. Two words on one + payment at one instant, or one after a failure, are refused. +* **A reopening auction computed from the venue's book** [FUT-033] (*phase 4*). + `TradingHalt(uncross=AuctionUncross(...))` prices a halt's reopening from the + `AuctionInterest` the venue published during it and the run's orders that + waited: the price matching the most volume, then the smallest imbalance, + then a declared tie-break (`AuctionTieBreak`); only the run's orders fill, by + price and then a declared priority against the published interest at the + marginal price (`AuctionPriority`). Nothing crosses without a book, without + a match, or outside the band, unless an extension is declared, during which + nothing trades. Each instrument's reopening is recorded as an + `alphalab.execution.AuctionReopening`, so a market-history bound no longer + mistakes a later event for the first (FUT-027's caveat). +* **What an amend holds, exactly** [FUT-036] (*phase 4*). An order reduced in + place holds, of the account's budget and of a ceilinged strategy's capital, + the share of its reservations the smaller order still adds -- an order + partly covering a short only its tail -- so the headroom it frees admits + another order in the same batch. Under FUT-029 the account's share was scaled + by quantity and the ceiling's held whole until the order ended. +* **An atomic amend** [FUT-031] (*phase 4*). An amend that replaces an order -- + more quantity, another limit or stop -- is prepared against the book without + the order and committed only if its replacement is placed: one that + allocation, risk or the venue's rules refuse leaves the original working, its + reservations held, and is an `AmendRefused` naming why, the decisions that + refused it kept on the record. The replacement is the quantity asked, never + sized by the run's sizing model. Under FUT-029 a refused replacement left the + original withdrawn. + +* **A strategy amends its own working order** [FUT-029] (*phase 3*). + `IntentKind.AMEND`: a reduction at the same prices is made in place and keeps + the order's id and queue priority; any other amend withdraws the order and + places its replacement through allocation, risk and the OMS (atomically since + FUT-031, phase 4). What cannot be amended -- no single order of the strategy's own, a netted or venue-held + order, other terms -- is recorded as `alphalab.strategy.AmendRefused`. +* **A halt reopening by auction** [FUT-027] (*phase 3*). `TradingHalt(auction=True)`: + at the first event after the halt, every order that rested through it -- + market orders, and limits the price reaches -- crosses at that event's + opening print, as a maker, by time and ahead of any queue model, within the + band in force; a market order's remainder is withdrawn, and what the price + does not reach trades on continuously. +* **Bands around a declared reference price** [FUT-028] (*phase 3*). + `alphalab.instrument.BandReference` declares a venue's dated reference, and + `ReferenceBand` a rule -- rates or absolute offsets either side, refuse or + cap, an optional staleness bound -- in `TradingControls.references` and + `reference_bands`. The band is the rule around the reference in effect, + rounded inward to the tick grid, read at placement, at every order's turn and + in a strategy's view; with no current reference every order rests. +* **Holder elections and announcement instants** [FUT-024] (*phase 3*). + `alphalab.backtesting.ElectiveExchange` offers a holding's strategies + alternatives -- each a `HoldingExchange` -- with a default, an announcement, a + deadline and an effective instant; a strategy elects with + `IntentKind.ELECT`, recorded as `alphalab.strategy.ElectionMade`, and the + holding is exchanged by its holders' common election between announcement + and deadline, or by the default. `TermsAdjustment.announced_at` keeps an + adjustment out of a strategy's view of its contract until it is announced. +* **Options repriced in margin scenarios** [FUT-026] (*phase 3*). + `ScenarioMargin.repriced` declares, per option, an + `alphalab.instrument.OptionPricing` -- volatility, rate, carry, and an American + contract's lattice steps -- and `MarginScenario.volatility_shifts` each + scenario's shifts. Each repriced option is valued from its underlying's move + and its shifted volatility (Black-Scholes, the lattice, or intrinsic value at + expiry) from the facts the execution path supplies + (`alphalab.instrument.OptionRisk`), and its loss is the model value's change: + the call, a liquidation's sizing and an order's pre-trade check all read it. + An option whose underlying is unpriced is margined by its own terms; a package, + quanto or Bermudan contract is refused at declaration. +* **A corporate action's cash paid on its payment date** [FUT-025] (*phase 3*). + `alphalab.backtesting.PaidExchange` declares a `HoldingExchange`'s payment + instant beside its effective one: the cash part and any cash in lieu are + booked as an entitlement, pending until paid -- out of buying power and + settled cash, as a sale's proceeds are -- and a restore rebuilds it. + `ExecutionPipeline.apply_exchange` and `RunEngine.apply_exchange` take + `payment_at`, keyword-only and `None` by default. +* **Interest and borrow fees accrued day by day** [FUT-023] (*phase 3*). + `alphalab.backtesting.DailyInterest` and `DailyBorrowFee` wrap a period with a + `DailyAccrual` -- an IANA zone and a cutoff -- and optional `RateChange`s: + each calendar day accrues at its cutoff on the balance (trade-date or + settled) or the short (at the day's mark and fee) it leaves, recorded + exactly as an `ACCRUAL` ledger transaction moving no cash, and the period + posts once, rounded once. `ScheduledInterest` and `ScheduledBorrowFee` are + unchanged, so a schedule declaring neither daily form keeps its digest. +* **A lender's recall, bought in** [FUT-022] (*phase 3*). `BorrowAvailability` + declares a recall's `buy_in_at` deadline; after it, a short beyond what the + entry makes available is bought in after each market step -- each short + strategy's own cover, a delta through allocation, risk and the OMS, a market + order resting to the asset's next event, filling as the market allows and + placed again for what it leaves -- and recorded as `alphalab.risk.BuyIn`. A + working market buy defers it; a resting limit does not. A run declaring no + recall records exactly what it did. +* **Every timed input on the historical drivers** [FUT-004, OFE-027]. + `alphalab.backtesting.schedule`: a `RunSchedule` of observations, FX rates, + fundings, cash flows, per-unit distributions (`ScheduledDistribution`), + perpetual funding rates (`ScheduledFundingRate`), splits, timers and contract + settlements, applied by one `ScheduleCursor` in `BacktestEngine.run(..., + schedule=)`, `ReplayBacktest.run(..., observations=, schedule=)`, + `alphalab.api.backtest`/`replay` and any loop written by hand -- each input + before or after the records of its instant, the rate table converting every + step. A book settling two currencies, an `on_timer` strategy, a dividend sized + by the position, a perpetual's funding and a split no longer need a hand loop. + `RunEngine.fund`. +* **A paused replay with information outstanding, kept durably** [FUT-003]. + Replay schema 2 records the schedule's digest; `replay_snapshot.restore(..., + schedule)` takes it back and refuses another or none. +* **Contracts expire and settle** [FUT-012]. `InstrumentRecord.last_trade_at`; + after it the instrument's records are skipped and its requests refused; + `ContractSettlement` (`RunEngine.settle_contract`, + `ExecutionPipeline.settle_contract`) closes every position at the final price + -- a future's last variation, an option's intrinsic value, zero for one + expiring worthless (`PortfolioEngine.apply_fill(..., final_settlement=True)`) + -- cancels working orders and attributes the settlement + (`AllocationEngine.settle_asset`); a run that ends holding an expired contract + is refused. +* **Bonds** [FUT-010]. `AssetType.BOND`, booked by declared economics; + `alphalab.api.bond_schedule` turns a `macro.bond.Bond` into per-unit coupons + and a redemption settlement. +* **Several datasets in one run** [OFE-028]. `alphalab.api.CombinedDataset`, + `CombinedProvenance`, `CombinedSourceBytes`, `combine_datasets`, + `combined_market_dataset`. +* **The schedule in a run's manifest** [FUT-005]. + `manifest_for_run(..., schedule=)` checks it against the run's deliveries and + records `ReproducibilityManifest.schedule_digest`, rendered into the identity + only when given, so every earlier manifest keeps its id. A run over a combined + dataset records `ReproducibilityManifest.dataset_parts`, checked against the + combined identity and listed part by part by `external_requirements`. +* **Execution algorithms on the historical drivers** [FUT-009]. + `alphalab.backtesting.algorithmic`: an `ExecutionPlan` of + `AlgorithmAssignment`s (an algorithm, a window, a quantity increment), by + asset or for every asset, with an optional `VenueCheck`, given to + `BacktestEngine.run(..., execution=)`, `ReplayBacktest.run(..., execution=)` + or `alphalab.api.backtest`/`replay`. The run routes + `ExecutionRouting.ALGORITHMIC`: each parent is accepted and held, worked in + children at its asset's events -- TWAP, VWAP, slicing, iceberg, participation + -- each child filled by the run's own fill policy and cost model through + `ExecutionPipeline.work_child` and settled on the parent; terms an algorithm + cannot work are refused before an order exists. The result is then an + `AlgorithmicResult` -- the same projection of the run, and the + `AlgorithmBook` beside it: every parent, child and fill (`ReplayResult.algorithms` + for a replay). `BacktestResult` itself still stores only the run. + A paused replay keeps it (`replay_snapshot.restore(..., execution=)`), and + `manifest_for_run(..., execution=)` records the plan's id. +* **A derivative's lifecycle, declared and carried out** [FUT-013]. + `InstrumentRecord.terms` declares `alphalab.instrument.OptionTerms` (call or + put, strike, `AMERICAN` or `EUROPEAN` with its window, `CASH` or `PHYSICAL`, + a `Deliverable` in any quantity per contract, an `AutomaticExercise` + threshold or none) or `FutureTerms` (cash, or delivery of a `Deliverable`). + `alphalab.portfolio.Exercise`, `Assignment` and `OptionExpiry` -- on the run's + schedule as `ScheduledExercise`, `ScheduledAssignment`, `ScheduledExpiry`, or + through `RunEngine.exercise` / `assign` / `expire_option` -- move cash, contracts + and the deliverable through the portfolio's fill authority and each + strategy's share; a physical future delivers at its final price. What cannot + be delivered or paid for is refused before anything moves. `AssetType.INDEX`, + a reference value never traded. `alphalab.runtime.session_close` and + `settles_at` read a lifecycle's instants from a declared market calendar and + settlement rule. +* **A queue model for resting limit orders** [FUT-011]. + `alphalab.execution.queue`: a `QueueModel` keeps each resting limit order's + `QueuePosition` -- the quantity ahead of it at its limit -- from what each + event showed (`QueueObservation`: the quote, the print and its reported + aggressor, the bar's range), and answers each turn with a `QueueTurn`: the + new position and what of the order may fill. `PriceTimeQueue` models + price-time priority over displayed liquidity: an order joins behind what was + shown at its price, or at the front inside the spread; prints at the limit by + the other side consume the queue first; a shrinking displayed level bounds + it; the market trading through the limit clears it; a touch fills only an + order at the front; an unsigned print counts only with + `count_unsigned_trades`. Given to `BacktestEngine.run(..., queue=)`, + `ReplayBacktest.run(..., queue=)` or `alphalab.api.backtest`/`replay`, it + bounds which passive fills happen -- the fill policy, simulator, costs, OMS, + portfolio and attribution are the run's own -- and the result is a + `QueuedResult` whose `execution_assumptions.queue_model` names it + (`ExecutionAssumptions.passive_fills` states how passive fills were decided, + with or without one). The `QueueBook` is driver state threaded through + `RunEngine.advance(..., queue=)` and `ExecutionPipeline.process_record` / + `process_market_event(..., queue=)`, so no run's record or `result_id` + moves; a paused replay keeps it (`alphalab.backtesting.queueing`, + `QUEUE_BOOK_SCHEMA = 1`; `replay_snapshot.restore(..., queue=)`), and + `manifest_for_run` records the model as `ReproducibilityManifest.queue_model`. + Refused beside an execution plan and on a run that routes to a venue. +* **Settlement-date accounting** [FUT-015]. `InstrumentRecord.settlement` declares + an instrument's settlement rule (a `SettlementRule`: trade date, T+n trading or + calendar days); each of its fills -- simulated, from a venue, a lifecycle + delivery or a final settlement -- is booked with its settlement instant by its + venue's declared calendar, holidays and time zone included, as metadata on its + own ledger transaction (`alphalab.portfolio.settlement`: `SETTLES_AT`, + `SETTLEMENT_CASH`). `PortfolioState.unsettled` (a derived index of + `PendingSettlement`s, rebuilt from the ledger on restore) and + `PortfolioEngine.cash_position` (a `CashPosition`: trade-date, unsettled credits + and debits, settled, available) report it; buying power excludes proceeds not + yet received, in the base currency, so an order needing them is refused until + they settle. An order in an instrument whose venue declares no calendar for its + rule is refused before it is placed. An instrument declaring no rule settles at + its trade and writes nothing new; no schema moves. +* **Margin calls and forced liquidation** [FUT-017]. `InstrumentRecord.margin` + declares an instrument's `alphalab.instrument.MarginTerms` -- a `MarginBasis` + (a rate of the position's value, or an amount per contract), an initial and a + maintenance requirement, a collateral haircut on a long, fully paid holding and + a liquidation priority -- kept by the registry snapshot, the registry indexing + the instruments that declare one (`InstrumentRegistry.margined`). + `alphalab.runtime.margin_position` reports the account's margin in the base + currency (a `MarginPosition` of `MarginHolding`s). After every market step an + account below maintenance is called -- an `alphalab.risk.MarginCall` on the risk + log -- and liquidated: the least that restores the initial requirement, in whole + lots or units, by priority, then the largest requirement, each holding + strategy's own target through allocation, risk and the OMS, resting to its + asset's next event and filling there by the run's fill policy. A step still in + breach calls again from the book as it is; a working liquidation is never + placed twice. While in breach only an order reducing a position trades; at any + time an order growing a margined position is refused unless equity stays at or + above the initial requirement, working orders counted. A margined future spends + its initial margin of buying power, not its notional. A physical delivery the + cash cannot pay is financed -- a debit, the consideration converted on margin in + another currency -- only when every deliverable declares margin terms: an + assignment, a future's delivery or an expiry's automatic exercise is booked and + called; an instructed exercise only within the initial requirement. A run + declaring no margin records exactly what it did; no schema moves. +* **Financing, margin purchases and short borrow** [FUT-018]. + `MarginTerms.purchase_on_margin` lets a fully paid instrument be bought on + margin: the purchase spends its initial requirement of buying power and the + cash it lacks is a financed debit. `alphalab.backtesting.ScheduledInterest` + charges a period's declared debit tiers (`alphalab.portfolio.FinancingTier`) + and optional credit rate on a currency's balance -- trade-date or settled + (`BalanceBasis`) -- for the period's dates by a `DayCount`, as an `INTEREST` + cash flow. `InstrumentRecord.borrow` declares + `alphalab.instrument.BorrowTerms`, a dated series of `BorrowAvailability` + (units available, or no stated limit, and an annual fee): a short beyond what + is available in effect, working sales counted, is refused before it is placed, + and `alphalab.backtesting.ScheduledBorrowFee` charges each short the fee in + effect on its value at the last mark. A strategy reads the borrow in effect + now through `context.universe.borrow(asset_id)` -- units available and the + fee, `(0, 0)` before the lender's first entry -- and never a later entry: + `context.universe.instrument` shows a record's borrow terms only as far as + they are in effect. A run declaring none of it records exactly what it did; + no schema moves. +* **A declared portfolio margin model** [FUT-019]. `alphalab.instrument.MarginModel` + reads every margined position at once; `PerPositionMargin` is FUT-017's sum, + and `ScenarioMargin` revalues the positions it covers under each declared + `MarginScenario` and requires the worst loss, floored, a position it does not + cover margined by its own terms. `alphalab.instrument.declare_margin_model` + puts one on the registry (`InstrumentRegistry.margin_model`, kept by its + snapshot), refusing one that covers what the registry does not margin. With a + model declared, the margin check, the liquidation's sizing (a convex search + that leaves a hedge leg alone when selling it would raise the requirement), + the pre-trade check and the risk gate's `MarginLimit` all read its figure. A + run declaring none is FUT-017's exactly. +* **Venue price bands and trading halts** [FUT-020]. `alphalab.instrument.TradingControls`, + declared on the registry by `declare_trading_controls` and kept by its + snapshot, holds dated `PriceBand`s (a limit down and up, and a `BandAction` + for an order beyond them, `REFUSE` or `CAP`) and `TradingHalt`s of an + instrument or a venue. At every order's turn nothing trades in a halt or at a + price outside the band: the order rests -- a market order to its first turn + after -- an immediate-or-cancel order is cancelled, a stop is not set off in a + halt, and an algorithm's child is withdrawn while its schedule goes on; a limit + beyond a band is refused or capped at placement. `context.universe.halted` and + `price_band` tell a strategy what is in force now. A run declaring no controls + records exactly what it did. +* **Mergers, spin-offs and rights issues** [FUT-021]. `alphalab.portfolio.HoldingExchange` + declares a holding's transformation by its issuer's terms -- `ExchangeLeg`s + (an instrument, units per unit held, the share of the basis it carries, an + in-lieu price for a fraction), a cash part, a retained basis share -- the + shares summing to one. `ExecutionPipeline.apply_exchange` and + `RunEngine.apply_exchange` (scheduled as `alphalab.backtesting.ScheduledExchange`) + apply it at its instant: the units the book can hold, a fraction in lieu or + refused, the basis apportioned and converted at the rate in force, the cash + part realizing its share -- a short receiving short legs and paying the cash -- + each strategy's share carried, working orders cancelled as at a split, and a + contract on the instrument refused unless adjusted. `PortfolioEngine.apply_exchange` + books it as an `EXCHANGE` ledger transaction and one `PositionExchanged` event. +* **A venue's price grid on the execution path** [EXE-013]. + `InstrumentRecord.tick` declares the instrument's `TickSchedule`, tiered or + flat, kept by the registry snapshot (`alphalab.conventions.tick.tick_to_primitives`, + `tick_from_primitives`). A limit or stop between two ticks of its own band is + refused at placement, naming the step, as is a negative one; a market order is + never refused for the grid; a band capping a limit lands it on the grid inside + the band. A run declaring no grid accepts any price, as before. +* **A strategy withdraws its own working orders** [EXE-012]. + `IntentKind.CANCEL`: a hook returns `Intent(kind=IntentKind.CANCEL, + instrument=, target=0)` and every order working in the asset that the + strategy alone contributed to is withdrawn at that event, after its fills and + before the batch's orders are placed -- a cancel and a new limit in one return + replace a quote. Its reservation is freed, a queue model holds nothing for it, + and the strategy hears it through `on_order`. An order netted with another + strategy's, a parent an execution plan works, or an order at a venue is left + working and recorded as an `alphalab.strategy.CancelRefused`. A run asking for + no cancel records what it did. +* **Richer contract terms** [FUT-016]. `OptionTerms` declares a `basket` of + further `Deliverable`s and a `cash_per_contract`; a `consideration_currency`, + converted at the exercise instant by the run's rates or by a quanto contract's + fixed `consideration_rate`; `fractions` (`alphalab.instrument.CashInLieu`) for a + fraction of a unit the book cannot hold; `ExerciseStyle.BERMUDAN` with + `exercise_windows`; and `adjustments` (`alphalab.instrument.TermsAdjustment`), + each effective from its instant -- `OptionTerms.split_adjusted` writes the one a + split implies. A physical package is planned before anything moves and booked + through the portfolio's fill authority, its cost apportioned by the components' + observed values; a cash-settled package, and a package's moneyness at expiry, + are valued at observed prices (`OptionExpiry.underlying_value` is optional). A + split of an instrument a held or working contract delivers is refused unless the + contract declares the adjustment the split implies. +* **A strategy exercises its own options from its hooks** [FUT-014]. + `IntentKind.EXERCISE`: any hook returns `Intent(kind=IntentKind.EXERCISE, + instrument=