Price gap fill

The price gap fill

A Held Price is the last priced observation of an asset, carried forward across a gap. Stating one is what PriceGapFill is for: it is the ingestion layer's only fill, it is off unless a caller adds it, and it exists to state a price convention across a suspension rather than to remove a gap — a gap is carried through the conversion, so nothing downstream needs it gone.

The fill is bounded by the Listing Span, so it touches Held Gaps alone and can never fabricate a price where an asset was not yet listed or has been delisted. It is fitted on a training window and replayed: CarriedPrice records the last observed training price, which seeds a carry-forward on a window that opens inside a gap, and a Num_VecToScaM records the reduction of that window's observed prices. A per-asset constant manufactures two moves the market never printed, which is why the carried price is the convention a caller reaching for a fill normally wants.

It runs at the price level, before PricesToReturns, because a carried price cannot be stated after the conversion: zeroing the returns a gap left non-finite discards the move across the gap entirely. A filled cell is therefore finite in the returns and its estimation mask entry is true — a caller who filled has said the asset traded. See docs/adr/0130-a-universe-policy-is-fitted-and-the-only-fill-is-a-span-bounded-price-convention.md.

PortfolioOptimisers.CarriedPriceType
struct CarriedPrice

States that a price did not move across a gap, so the last priced observation is held forward.

CarriedPrice is the Held Price convention: inside a Held Gap the price is the last observed one, so the returns across the gap are flat and the whole move lands on the observation that ends it. Wealth is conserved, which a per-asset constant cannot do — filling a suspension with an asset's median manufactures one large move into the gap and another out of it.

It names a convention rather than a value, so it is the one member of PriceGapFill's fill slot that is not a number: the value it fills with is read off the data, and the fitted state is only the seed a later window that opens inside a gap starts from.

Constructors

CarriedPrice() -> CarriedPrice

Examples

julia> CarriedPrice()CarriedPrice()

Related

source
PortfolioOptimisers.PriceGapFillType
struct PriceGapFill{__T_fill, __T_strict, __T_cache} <: AbstractPricesPreprocessingEstimator

Fills the price gaps inside an asset's listing with a stated convention, and touches nothing outside it.

PriceGapFill is the ingestion layer's one fill, and it is off unless a caller adds it. A fill exists to state a price convention across a suspension or a holiday, not to remove a gap: a gap is carried through the conversion, so nothing downstream needs it gone. The fill is bounded by the Listing Span, so it touches Held Gaps alone and can never fabricate a price where an asset was not yet listed or has been delisted.

It runs at the price level, before PricesToReturns, because a carried price is a statement about a price and cannot be expressed after the conversion: across a gapped run p₀, _, _, p₃ the unfilled returns are all non-finite, and zeroing them discards the p₀ → p₃ move entirely. Carried forward at the price level the same run gives 0, 0, p₃/p₀ - 1. A filled cell is therefore finite in the returns and its estimation mask entry is true, which is the design rather than an oversight: a caller who filled has said the asset traded.

Algorithm

Fit

  1. For each asset column, collect the observed prices of the training window. An entry is_missing_value accepts is left out.
  2. Skip an asset whose column holds no observed price. It gets no fitted value, and no entry in the result, so it is left untouched at apply time.
  3. Reduce the observed prices of the column to one value with PortfolioOptimisers.gap_fill_seed, giving that asset's fitted value.
  4. Return a PriceGapFillResult holding the fitted asset names, their values, the last timestamp of the training window, the convention and strict.

Apply

  1. Copy the price values of the window, so the input is not mutated.
  2. Read the Listing Span that bounds the fill with PortfolioOptimisers.gap_fill_span. A carrier that states none bounds the fill by nothing, so no cell is written.
  3. Find the first observation of the window after the training window's end, t0. On the training window itself there is none; on a window that follows it, t0 is the first row.
  4. For each fitted asset name, find its column in the window. Skip a name the window does not carry.
  5. Run the convention forward through that column with PortfolioOptimisers.gap_fill_column!, seeded by the fitted value from t0 and bounded by the span.
  6. Rebuild X from the filled values, keeping the timestamps and the column names, then rebuild the PricesResult. Every other field passes through untouched.

The fitted value is a seed, and under CarriedPrice it is only that: a window that opens inside a gap after the training window fills from the last observed training price, and every later gap of that window fills from the most recent price the window itself observed. The seed is the last training price, so it precedes every observation it is written onto, and it is written onto none before that: on the training window itself, which a Pipeline transforms with the step it just fitted, a gap that opens the window has no price before it and stays a Held Gap rather than taking a price from the window's end. So no fill reads the future, and the convention still tracks the data it is replayed on.

Fields

  • fill: The convention a gap inside the Listing Span takes. CarriedPrice holds the last priced observation forward; a Num_VecToScaM reduces the asset's observed training prices to the one value every gap of that column takes.
  • strict: Whether a price carrier that states no Listing Span is refused (true) or warned about (false).
  • cache: Optional partial-fit state. It is nothing until partial_fit! writes one, and the estimator's read-out verb reads it when the caller gives no data matrix. Each propagation channel does one thing with it: factory carries it unchanged, because a factory call resolves configuration rather than the sample; port_opt_view slices it to the selected assets by index copy, so the viewed estimator answers over those assets alone; and obs_weights_view drops it, because no slice of a state exists on the observation axis. A family whose state has no exact asset slice drops it on both axes and names the reason.

Constructors

PriceGapFill(;    fill::Union{CarriedPrice, Num_VecToScaM} = CarriedPrice(),    strict::Bool = false,    cache::Option{<:AbstractPartialFitState} = nothing,) -> PriceGapFill

Keywords correspond to the struct's fields.

Online form

A PriceGapFill with a CarriedPrice fill takes the online step: partial_fit_transform fills a block of prices from the carried prices and advances them, and fit_preprocessing with no data reads the PriceGapFillResult of the whole history out of cache. A statistic fill has no online form, because a longer window re-prices every earlier gap; supports_partial_fit answers false for it, and a Pipeline refuses it at warm-up by name.

Examples

julia> X = TimeArray(Date(2020, 1, 1):Day(1):Date(2020, 1, 4),                     [100.0 1.0; NaN 3.0; NaN 5.0; 130.0 7.0], ["A", "B"]);julia> res = fit_preprocessing(PriceGapFill(), PricesResult(; X = X));julia> res.v2-element Vector{Float64}: 130.0   7.0

Related

source
PortfolioOptimisers.PriceGapFillResultType
struct PriceGapFillResult{__T_nx, __T_v, __T_te, __T_fill, __T_strict} <: AbstractPricesPreprocessingResult

Fitted result of a PriceGapFill.

Carries the per-asset value fitted on the training window, the timestamp that window ended on, and the convention the value is read under. One result type serves both conventions, as AssetSelectorResult serves the whole selector family: the convention is a field rather than a second type, because it is what apply_preprocessing dispatches on and nothing else about the two differs.

Fields

  • nx: Names of the assets with a fitted value.
  • v: Fitted values, aligned with nx. The last observed training price under CarriedPrice, and the reduced scalar under a Num_VecToScaM.
  • te: The last timestamp of the training window. Under CarriedPrice the seed in v is written onto an observation after it and onto none at or before it, so a window that opens inside a gap fills from the seed only when the seed precedes it; a Num_VecToScaM reads it by nothing.
  • fill: The convention v is read under, copied from the estimator.
  • strict: Whether a price carrier that states no Listing Span is refused (true) or warned about (false).

Related

source